Skip to content

Latest commit

 

History

History
247 lines (173 loc) · 10.9 KB

bufrscan.md

File metadata and controls

247 lines (173 loc) · 10.9 KB

bufrscan.rb - 低レベルBUFRデコーダ

BUFR 表を必要としない固定位置情報(第1節など)の解読と、 ビット列の解読を行います。 BUFR の構造については BUFR.md を参照してください。

単独での使用

ruby bufrscan.rb files ...

任意の形式のファイル files を開いて BUFR 報をすべて抽出し、 各 BUFR 報について第1節などの主要部情報 (TBD) を JSON 形式で印字します。

$ ruby bufrscan.rb tests/A_ISMC01RJTD270000_C_RJTD_20191027001931_55.bufr
{"msglen":8970,"ed":4,
 "meta":{"ahl":"ISMC01 RJTD 270000",
 "fnam":"tests/A_ISMC01RJTD270000_C_RJTD_20191027001931_55.bufr","ofs":21},
 "mastab":0,"ctr":34,"subctr":0,"upd":0,"cat":0,
 "subcat":2,"masver":13,"locver":0,"cflag":false,
 "nsubset":52,"obsp":true,"compress":false,
 "descs":"301090,302031,302035,302036,302047,008002,302048,302037,302043,302044,101002,302045,302046",
 "reftime":"2019-10-27 00:00:00 UTC"}

※読みにくいので折り返して表示していますが実際には JSON は一行です。

ヘッダ選択オプション

ruby bufrscan.rb file:AHL=regexp ...

ファイル名に :AHL=regexp を後置すると、 BUFR 報の直前に GTS 電文ヘッダ行が認識できる場合、 正規表現 regexp にマッチしないヘッダ行がある BUFR 報を処理対象から除外します。

たとえば obsbf-2019-11-06.tar:AHL=^IS であれば ヘッダ行が IS で始まる BUFR 報が選ばれます。 キャレット ^ をつけないと、ヘッダ行の途中に IS がある場合も対象となります。 字数が多ければ、 たとえば :AHL=RJTD であればおそらくヘッダ先頭ではなく発信中枢略号が RJTD (日本東京気象庁)が選ばれるものと思われます。保証はないけど手早いです。

警告: 正規表現は Ruby の正規表現です。 任意コード実行ができる Perl の正規表現ほど危険ではありませんが、 工夫すれば無限ループによる DOS 攻撃くらいはできるので、 信用できない他人がもたらした入力を正規表現にするのはやめましょう。

出力内容変更オプション

ruby bufrscan.rb -d files ...

各 BUFR 報について記述子列をコンマ区切りで印字します。 集約を展開しないので、第3節に書かれている記述子列がそのまま得られます。

$ ruby bufrscan.rb -d tests/A_ISMC01RJTD270000_C_RJTD_20191027001931_55.bufr
301090,302031,302035,302036,302047,008002,302048,302037,302043,302044,101002,302045,302046

ruby bufrscan.rb -fctr files ...

各 BUFR 報について発信中枢番号をコンマ区切りで印字します。

ライブラリ

class BUFRScan

任意の形式のファイルについて BUFR 報を抽出し BUFRMsg クラス値を構築します。

BUFRScan.filescan(fnam) {|msg| ... }

基本的にこれを使ってください。

ファイル fnam をバイナリモードで開き、BUFR 報が見つかるたびに yield で BUFRMsg クラスの値を投げ返します。

BUFRScan.new(io, fnam = '-')

既に開かれている io (必ずしもディスクファイルである必要はなく、 read(n) が文字列を返す何か)について BUFRScan を構築して返します。

BUFRScan#scan {|bufrmsg| ... }

構築時に与えた io を読みだして BUFR 報が見つかるたびに BUFRMsg を構築して yield で投げ返します。

class BUFRMsg

BUFRMsg.new buf, ofs, msglen, fnam = '-', ahl = nil

文字列 bufofs オクテット目(先頭がゼロ)から msglen 個オクテットを BUFR 報として解読する準備を始めます。

必須ではありませんがファイル名 fnam や GTS ヘッダ ahl を与えると エラーメッセージがわかりやすくなります。

このクラス内部には、readnum または readstr でビット単位の読み出しを行う 現在位置ポインタが保持され、その初期値は第4節のビット列の先頭にセットされます (後述 ptrcheck 参照)。

BUFRMsg#to_h

固定位置に書かれた情報、つまり第1節各欄および 第3節(サブセット数、フラグ、記述子列)を解読し、 ハッシュで返します。

key 節#位置
:msglen (newの引数) 電文長/オクテット
:ed 0#7 BUFR 版数
:meta (newの引数) {:ahl=>ヘッダ, :fnam=>ファイル名}
:mastab 1#3 マスター表番号(分野番号)
:ctr 1#4-5 (ed3: 1#5) 発信中枢番号
:subctr 1#6-7 (ed3: 1#4) 発信副中枢番号
:upd 1#8 (ed3: 1#6) 更新一連番号
:cat 1#10 (ed3: 1#8) カテゴリ番号
:subcat 1#12 (ed3: 1#9) サブカテゴリ番号(ed4: 国際管理, ed3: 作成中枢管理)
:masver 1#13 (ed3: 1#10) マスター表のバージョン番号
:locver 1#14 (ed3: 1#11) ローカル表のバージョン番号
:reftime 1#15-21 (ed3: 1#12-16) 参照日時
:nsubset 3#4-5 サブセット数
:obsp 3#6 & 0x80 観測データの時に真
:compress 3#6 & 0x40 圧縮データの時に真
:descs 3#7-- (コンマ区切り文字列の形で)記述子列
  • :meta をひとまとめにしてあるのは、 それだけ除いて比較すれば内容的に同一の電文を検出しやすいという配慮ですが、 いまのところ活用されていません。
  • :subcat に BUFR 第3版の作成中枢管理のサブカテゴリ番号を保存したのは 規則的にはきれいではありませんが、国際管理サブカテゴリ番号と互換に運用して いる発信中枢もあり、そうでないところもあり、ちょっと悩ましいところです。

BUFRMsg#[key]

to_h[key] を返します(なければ nil)。

BUFRMsg#ahl

構築時に与えられたヘッダ行(なければ nil)を返します。

同じ結果が self[:meta][:ahl] としても得られるのですが、 呼び側コードのレビューとして カッコ参照が二重になると前段 [:meta] == nil にならない保証があるか (あるけど) 不安になるだろうのであえて用意しています。

BUFRMsg#compressed?

BUFR 第1節のフラグにより圧縮形式とされる場合に真を返します。

BUFRMsg#readnum desc

現在位置から desc[:width] 個ビットを読み出し、 参照値 desc[:refv] を加算したあと 尺度 desc[:scale] だけ小数点の桁を上げた値を返します。 現在位置は読みだしたビット数だけ進みます。 圧縮時はサブセット数の要素を有する配列が返ります。 返却値は尺度がゼロの場合 Integer さもなくば Float です。 読みだしたビットがすべて立っていれば欠損値 nil が返りますが、 通報式の規定により 記述子番号文字列 desc[:fxy] が操作記述子 204YYY であるか クラス 31 の遅延反復数の場合は欠損処理は行われません。

ただし特例として メキシコ不正 SYNOP 救済 (#12) のため、 圧縮のない電文においてクラス 31であり 031001 でも 031031 でもない場合は 全ビットが立っていると 特例として欠損値として読み出して後処理にゆだねます。

  • 現在位置から第4節末尾までに必要なビット数がない場合、 例外 BUFRMsg::ENOSPC が発生します。

BUFRMsg#readstr desc

現在位置から desc[:width] 個ビットを読み出し、 これを ISO-8859-1 による文字列として返します。 圧縮時はサブセット数の要素を有する配列が返ります。

  • 圧縮時の参照値として通報式の規定によるヌル文字と欠損値が許容されます。 どちらでもない場合には例外 BUFRMsg::EBADF が発生します。
  • 現在位置から第4節末尾までに必要なビット数がない場合、 例外 BUFRMsg::ENOSPC が発生します。

BUFRMsg#ptrcheck

現在位置(ビット単位)と、第4節ビット列末尾の位置(ビット単位)の 2要素からなる配列が返ります。

BUFRMsg#getnum ptr, width

ビット位置 ptr からビット数 width を読み出し Integer 値として返します。 現在位置は参照も移動もしません。

  • 第4節の外のビット位置を読むかどうかは検査されません。

BUFRMsg#ptrseek ofs

現在位置を ofs ビット進めます。

  • 第4節の外のビット位置まで進むかどうかは検査されません。

BUFRMsg#ymdhack opts

各サブセット冒頭のビット位置で呼び出す日付チェックです。 記述子列冒頭の「年月日」(004001,004002,004003) のビット位置を opts[:ymd] で与えた場合、 その年月日を取得して、 電文の参照日時 self[:reftime] と同一日付、前後日付、 同一月の参照日以前、または欠損値、 あるいは参照日が月末の場合同一月の末日である場合が許容されます。

許容できない日付(つまり年月が異常値)の場合、 これまでの解読(ほとんどの場合遅延反復数の誤り)によって現在位置がずれている ものと推定し、ヒューリスティックな方法でサブセット先頭の ビット位置を発見しようと試みます。 ここで opts['001011'] または opts['001015'] に当該記述子のビット位置を 与えると発見能力が高まります。

  • 圧縮形式の場合ビット位置は予測困難なのでなにもせず終了します。
  • サブセット位置発見に失敗すると、例外 BUFRMsg::ENOSPC が発生します。

例外

class BUFRMsg::ENOSPC

電文内のビット位置の把握に失敗して処理が継続できなくなったときに発生します。 次の電文の処理に移るべきです。

Errno::ENOSPC を継承しているのでその名前でも捕捉できます。

BufrDecode.decode 内部で発生した場合は そこで捕捉されて次の電文の処理に移ります。

class BUFRMsg::EBADF

ビット位置以外の何らかのフォーマットエラーを検出したときに発生します。 BUFRScan#scan 内部で発生した場合はそこで捕捉され次の電文の処理に移ります。

Errno::EBADF を継承しているのでその名前でも捕捉できます。

class BUFRMsg::ENOSYS

何らかの未実装機能を検出したときに発生します。 現状では BUFR 版数の異常で発生します(つまりいつの日か BUFR 第5版を与えたとき)。 BUFRScan#scan 内部で発生した場合はそこで捕捉され次の電文の処理に移ります。

Errno::ENOSYS を継承しているのでその名前でも捕捉できます。