PyBufrKitを使って流域雨量指数BUFRをデコードする

# はじめに

気象庁の[洪水警報の危険度分布](https://www.jma.go.jp/jma/kishou/know/bosai/riskmap_flood.html)の基となる[流域雨量指数](https://www.jma.go.jp/jma/kishou/know/bosai/ryuikishisu.html)というデータがある。このデータは河川の流量に相当する量を表し、3次メッシュコードと河川コードごとに値が定義されており、BUFR4形式で格納されている。BUFRをデコードする方法は様々だが、流域雨量指数では気象庁独自のローカルテーブルが用いられているため、どのツールでもローカルテーブルを指定しないと読み出すことができない。

「流域雨量指数 BUFR」で検索すると、[BUFR4フォーマットの解読方法](http://www.eqh.dpri.kyoto-u.ac.jp/~masumi/sac/bufr4.htm)という、カナダのBUFRライブラリ[libECBUFR](https://github.com/ECCC-MSC/libecbufr)を用いた方法がヒットする。もちろんこれでもよいのだが、インストールがややめんどくさいこと、最近流行りのPythonを使うと何かと便利なので、[PyBufrKit](https://pybufrkit.readthedocs.io/en/latest/)と呼ばれるPythonパッケージの使い方を調査した。

# 使い方
## インストール
`conda`ではインストール出来ないようなので、`pip install pybufrkit`を叩く。

## ローカルテーブルの指定

### 流域雨量指数BUFR用いられるテーブルの確認

まず[配信資料に関する技術情報第446号](https://www.data.jma.go.jp/add/suishin/jyouhou/pdf/446.pdf)を読んで、流域雨量指数BUFRで用いられるテーブルに関するパラメータを確認する。
<img width="500" alt="bufr_a.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/829116/920ac5ac-dec8-995d-305b-af76e96f9a13.png">

あるいは`pybufrkit`コマンドを叩いても、エラーは出るが確認できる。テーブルに関するパラメータは以下の通り。

```text:info
$ pybufrkit info (BUFRファイル)
(略)
master_table_number = 0
originating_centre = 34
originating_subcentre = 0
master_table_version = 26
local_table_version = 1
```

### マスターテーブルのダウンロード

上記のパラメータに対応するPyBufrKit用のJSON形式のマスターテーブルを、[PyBufrKitのGitHub](https://github.com/ywangd/pybufrkit)からダウンロードする。読み込まれるファイルは`tables/{master_table_number}/0_0/{master_table_version}`内にある`TableB.json`と`TableD.json`である（同じディレクトリ内に`code_and_flag.json`もあるが、今回は無くても動いた）。したがって今回の場合、[https://github.com/ywangd/pybufrkit/tree/master/pybufrkit/tables/0/0_0/26](https://github.com/ywangd/pybufrkit/tree/master/pybufrkit/tables/0/0_0/26)の`tables`以下をダウンロードすればよい。このダウンロードした`tables`ディレクトリの場所は、実行時に指定するため、好きな場所に置くことができる。

ダウンロードしたJSON形式のテーブルの中身を確認してみよう。まずはTableBから。

```json:0/0_0/26/TableB.json
{"000001": ["TABLE A: ENTRY", "CCITT IA5", 0, 0, 24, "Character", 0, 3]}
```

この意味は[WMOのTableBの仕様書](https://www.wmo.int/pages/prog/www/WMOCodes/WMO306_vI2/LatestVERSION/WMO306_vI2_BUFRCREX_TableB_en.pdf)を見るとわかりやすい。
<img width="700" alt="TableB.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/829116/970016b7-0f43-7840-b668-af8ceb7db4e6.png">
うしろ3つはCREX用で使われないため、ローカルテーブルを自作するときはダミーで埋めてよい。

つぎにTableDを見てみる。

```json:0/0_0/26/TableD.json
{
	"300002": ["", ["000002", "000003"]],
	"300003": ["(F, X, Y of descriptor to be added or defined)", ["000010", "000011", "000012"]]
}
```

これについても[WMOのTableDの仕様書](https://www.wmo.int/pages/prog/www/WMOCodes/WMO306_vI2/LatestVERSION/WMO306_vI2_BUFR_TableD_en.pdf)と比較してみると意味が分かる。
<img width="600" alt="TableD.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/829116/e256807c-0aad-0030-769c-cdd10a7e7aaf.png">

### JMA用ローカルテーブルの作成
テーブルの書式が分かったところで、JMAのローカルテーブルを以下のディレクトリに作成する。
`tables/{master_table_number}/{originating_centre}_{originating_subcentre}/{local_table_version}/Table{B,D}.json`
具体的には`tables/0/34_0/1/Table{B,D}.json`となる。

まずTableBについて、[配信資料に関する技術情報第446号](https://www.data.jma.go.jp/add/suishin/jyouhou/pdf/446.pdf)の「要素記述子」という表を見る。
<img width="500" alt="TableB_JMA.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/829116/59661f09-8086-f155-2188-e840353889ec.png">

したがって、以下のファイルを作成すれば良い。

```json:0/34_0/1/TableB.json
{
    "008021": ["TIME SIGNIFICANCE", "CODE TABLE", 0, 0, 5, "CODE TABLE", 0, 2],
    "004001": ["YEAR", "YEAR", 0, 0, 12, "YEAR", 0, 4],
    "004002": ["MONTH", "MONTH", 0, 0, 4, "MONTH", 0, 2],
    "004003": ["DAY", "DAY", 0, 0, 6, "DAY", 0, 2],
    "004004": ["HOUR", "HOUR", 0, 0, 5, "HOUR", 0, 2],
    "004005": ["MINUTE", "MINUTE", 0, 0, 6, "MINUTE", 0, 2],
    "001210": ["RIVER CODE", "Numeric", 0, 80000000, 24, "Numeric", 0, 8],
    "005240": ["INDEX OF LATITUDE FOR 1ST ORDER MESH", "Numeric", 0, 0, 7, "Numeric", 0, 4],
    "006240": ["INDEX OF LONGITUDE FOR 1ST ORDER MESH", "Numeric", 0, 0, 7, "Numeric", 0, 4],
    "005241": ["INDEX OF LATITUDE FOR 2ND ORDER MESH", "Numeric", 0, 0, 4, "Numeric", 0, 2],
    "006241": ["INDEX OF LONGITUDE FOR 2ND ORDER MESH", "Numeric", 0, 0, 4, "Numeric", 0, 2],
    "005242": ["INDEX OF LATITUDE FOR 3RD ORDER MESH", "Numeric", 0, 0, 4, "Numeric", 0, 2],
    "006242": ["INDEX OF LONGITUDE FOR 3RD ORDER MESH", "Numeric", 0, 0, 4, "Numeric", 0, 2],
    "013212": ["RUNOFF INDEX (HIGH ACCURACY)", "Numeric", 1, 0, 12, "Numeric", 0, 4]
}
```

TableDについても同様に、[配信資料に関する技術情報第446号](https://www.data.jma.go.jp/add/suishin/jyouhou/pdf/446.pdf)の「集約記述子」という表を見る。
<img width="300" alt="TableD_JMA.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/829116/ba7a2d07-db86-ff2a-f042-722f8ea8c88b.png">

したがって、以下のファイルを作成すれば良い。

```json:0/34_0/1/TableD.json
{
    "301011": ["", ["004001", "004002", "004003"]],
    "301012": ["", ["004004", "004005"]],
    "301200": ["", ["005240", "006240", "005241", "006241", "005242", "006242"]]
}
```

## コマンドラインでの使い方

```text:help
$ pybufrkit -h
usage: pybufrkit [-h] [-v] [--info | --debug] [-d DEFINITIONS_DIRECTORY]
                 [-t TABLES_ROOT_DIRECTORY]
                 command ...
```

- `-t`で作成したテーブルのディレクトリを指定する
    - 参考：[https://github.com/ywangd/pybufrkit/issues/5](https://github.com/ywangd/pybufrkit/issues/5)
- ファイル情報のみを表示
    - `pybufrkit -t ./tables info ${bufrfile}`
- テキスト形式にデコードする（かなり冗長）
    - `pybufrkit -t ./tables decode ${bufrfile}`
- JSON形式にデコードする（値だけが格納される）
    - `pybufrkit -t ./tables decode -j ${bufrfile}`

## Pythonモジュールとしての使い方

JSONに変換するサンプルプログラム

```python:bufr2json.py
import os
from pybufrkit.decoder import Decoder
from pybufrkit.renderer import FlatJsonRenderer


class BufrConverter:
    def __init__(self):
        self.TOPDIR = os.path.dirname(os.path.abspath(__file__))
        self.decoder = Decoder(tables_root_dir=f"{self.TOPDIR}/tables")
        self.renderer = FlatJsonRenderer()

    def convert(self, ini, region):
        filename = f"Z__C_RJTD_{ini}_MET_SEQ_Ggis1km_Proi_Aper10min_RJsuikei{region}_ANAL_bufr4.bin"
        filepath = f"{self.TOPDIR}/bufr/{filename}"
        with open(filepath, 'rb') as buf:
            bufr_message = self.decoder.process(buf.read())
        json = self.renderer.render(bufr_message)
        return json
```

## 作業中に出た警告とその意味のメモ

これらの警告を解消しないと、JSONに出力したときなどにエラーメッセージも同時にはき出されて邪魔であった。参考までにメモ。

- `WARNING: Fallback to default master table version 33 (26 not found)`
    - BUFRで指定されているマスターテーブル`tables/0/0_0/26`がなく、最新版の`tables/0/0_0/33`が存在する場合、そちらにフォールバックされる
- `WARNING: Cannot find sub-centre 0 nor valid default. Local table not in use.`
    - BUFRで指定されているローカルテーブル`tables/0/34_0/1`が見つからない場合、ローカルテーブルは用いられない