0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

線状降水帯予測プロトタイプを作ってみる【r001】Step 1 完了編 ー Open-MeteoとDIASのMSMを共通形式へそろえる

0
Posted at

線状降水帯予測プロトタイプ Step 1 完了編 ー Open-MeteoとDIASのMSMを共通形式へそろえる

はじめに

線状降水帯の予測方法を試すため、まずは数値予報データを安定して扱えるところから作っています。

r001では、いきなり「線状降水帯を判定する」処理には進みません。

最初のStep 1では、

  • Open-Meteo経由でJMA MSMを取得する
  • DIASから気象庁MSMの実GRIB2を取得する
  • 取得元の違いを吸収する
  • 時刻、単位、格子の持ち方を共通化する
  • Zarrへ保存する
  • 後から同じデータを再検証できるようにする

ところまでを作りました。

今回、fix012をUbuntu 24.04 + Docker環境で実データまで通し、Step 1の完了条件をすべて確認できました。

最終的な流れは次のようになります。

20260915_s01.jpg

Step 2以降は、Open-MeteoかDIASかを意識せず、この共通データだけを使います。


Step 1の完成条件

Step 1では、次の状態までを完成条件にしました。

20260915_s02.jpg

また、ソースについても、

Docker build
Ruff format
Ruff check
mypy
pytest
source contract
package source check

をすべて通すことにしました。


1. 実行環境

今回確認した環境です。

OS          Ubuntu 24.04
Container   Docker Engine / Docker Compose
Python      Dockerコンテナ内
計算基盤    NumPy
GRIB2       cfgrib + ecCodes
格子保存    xarray + Zarr
静的検査    Ruff / mypy
テスト      pytest

Step 1はデータ取得と形式変換が中心なので、GPUは使っていません。

Numbaもまだ必須にはしていません。

3時間積算や雨域処理を実装するStep 2以降で、実際の処理時間を計測してから必要な箇所だけ使う予定です。


2. fix012をビルドする

参考のために、ZIPファイルを置いておきますので、ダウンロードして活用してください。

まず環境変数ファイルを作ります。

cp .env.example .env

sed -i "s/^LOCAL_UID=.*/LOCAL_UID=$(id -u)/" .env
sed -i "s/^LOCAL_GID=.*/LOCAL_GID=$(id -g)/" .env

Docker imageをクリーンビルドします。

docker compose build --no-cache

今回の実機では、Ubuntu 24.04をベースにしたimageが正常に作成できました。

Image linear-rainband-step1:r001-fix012 Built

3. 配布物を8項目で検証する

ビルド後、Step 1の検証スクリプトを実行します。

docker compose run --rm step1 \
  ./scripts/validate_package.sh

最終結果は次のようになりました。

[1/8] Environment / dependency consistency
No broken requirements found.
linear-rainband-step1 0.1.12
ruff 0.16.7
mypy 1.20.2

[2/8] Shell / Python syntax

[3/8] ruff format
25 files already formatted

[4/8] ruff check
All checks passed!

[5/8] mypy
Success: no issues found in 18 source files

[6/8] pytest
........................... [100%]

[7/8] CLI / source contract
SOURCE CONTRACT PASS

[8/8] package source files
PACKAGE SOURCE CHECK PASS

PASS: Step 1 fix012 package validation

ここで、

  • Python構文
  • Shell構文
  • Ruff format
  • Ruff lint
  • mypy
  • pytest
  • CLI
  • Provider契約
  • 配布ファイル構成

までをまとめて確認しています。


4. Open-MeteoからJMA MSMを取得する

最初のデータ源としてOpen-MeteoのJMA MSMを使います。

実行は次のコマンドです。

docker compose run --rm step1 \
  rainband-step1 fetch-openmeteo \
  --config config/step1.yaml

今回の設定では、

緯度       34.60 ~ 35.00
経度       137.50 ~ 138.00
緯度間隔   0.05°
経度間隔   0.0625°
予報時間   12時間

を対象にしています。

実際の取得結果は、

run_id   latest_20260914T141745Z
shape    (12, 9, 9)

となりました。

内部では、

Open-Meteo API
      ↓
地点ごとの時系列
      ↓
time × latitude × longitude
      ↓
ForecastRun
      ↓
Zarr
      ↓
manifest

という形に変換しています。


5. Open-Meteo側の保存結果をverifyする

生成したmanifestを指定して再検証します。

docker compose run --rm step1 \
  rainband-step1 verify \
  --manifest \
  /workspace/data/manifests/openmeteo_jma_msm/latest_20260914T141745Z.json

結果は、

run_id    latest_20260914T141745Z
raw_files 2
grid      ...latest_20260914T141745Z.zarr
status    PASS

でした。

manifestの中身も確認しました。

{
  "dimensions": {
    "time": 12,
    "latitude": 9,
    "longitude": 9
  },
  "variables": [
    "precipitation",
    "source_latitude",
    "source_longitude"
  ],
  "time_start": "2026-09-14T14:00:00.000000000",
  "time_end": "2026-09-15T01:00:00.000000000",
  "time_interval_hours": [
    1.0
  ],
  "precipitation_min_mm": 0.0,
  "precipitation_max_mm": 85.0999984741211,
  "nan_count": 0
}

単にAPIへ接続できたというだけではなく、

  • 実際の降水予測値が入っている
  • 時間間隔が1時間
  • 欠測がない
  • Zarrとして保存できる
  • 保存後に再読込できる
  • manifestから再検証できる

ところまで確認しています。


6. DIASから実MSM GRIB2を取得する

DIASアカウント申請を登録して、ログインできることを確認してください。登録できていないのGPVのデータはダウンロードできません。

次に、Open-Meteoとは別経路で、DIASに保存されている気象庁MSMの実GRIB2を使います。

DIASの対象データセットは、

Dataset ID: GPV

です。

Step 1ではMSMの地上面データだけを対象にしました。

検索対象は、

MSM_GPV_Rjp_Lsurf

です。

今回は1つの初期時刻だけに絞り、

2026-06-12 21:00 UTC

について、次の3ファイルを取得しました。

Z__C_RJTD_20260612210000_MSM_GPV_Rjp_Lsurf_FH00-15_grib2.bin
Z__C_RJTD_20260612210000_MSM_GPV_Rjp_Lsurf_FH16-33_grib2.bin
Z__C_RJTD_20260612210000_MSM_GPV_Rjp_Lsurf_FH34-39_grib2.bin

DIASの共通ダウンロードシステムで検索結果をこの3ファイルに絞り、公式のdownload.pyを生成します。

取得には、この公式スクリプトを使います。

./scripts/run_dias_official_download.sh \
  ~/vol/linear_rainband_r001_step1_fix012/download.py

実際の結果は、

file_hashes.csv  OK

...Lsurf_FH00-15_grib2.bin  OK
...Lsurf_FH16-33_grib2.bin  OK
...Lsurf_FH34-39_grib2.bin  OK

DIAS download finished.

となりました。

DIAS内部のURLを推測してスクレイピングする処理は入れていません。

公式Web画面と公式download.pyを使っています。

usernameとpasswordが必要です。


7. 分割されたMSMファイルを確認する

ダウンロード後、Step 1側からファイルを確認します。

docker compose run --rm step1 \
  rainband-step1 list-dias

結果は、

init_time_utc              segments                   files
2026-06-12T21:00:00+00:00  FH00-15, FH16-33, FH34-39  3

でした。

つまり、

2026-06-12 21:00 UTC
│
├─ FH00-15
├─ FH16-33
└─ FH34-39

が、同じ初期時刻に属する3分割ファイルとして正しく認識できています。


8. 実MSM GRIB2から降水量を取り出す

ここは実データを使って初めて分かった点でした。

最初は、cfgribが返す変数名からprecipitationやtpを探していました。

しかし、実際のJMA MSM GRIB2では、その方法では降水量を安定して特定できませんでした。

そこで、変数名ではなく、GRIB2の公式パラメータ番号を使います。

MSM地上面の総降水量は、

parameterCategory = 1
parameterNumber   = 8

です。

Step 1では、

GRIB2
  ↓
parameterCategory = 1
parameterNumber   = 8
  ↓
総降水量

として直接抽出します。

これでcfgrib側の変数名に依存しなくなりました。


9. 実MSMでは降水量の単位がunknownになった

もう1点、実GRIB2を使って分かったことがあります。

cfgrib/ecCodesで総降水量を読み込むと、今回のファイルでは単位文字列が、

unknown

になりました。

そのままでは、

ValueError:
unsupported precipitation unit in MSM GRIB2: 'unknown'

として停止します。

ここでunknownを何でも許可するのは危険です。

そこでfix012では、

DIAS MSM Lsurf
      ↓
parameterCategory = 1
parameterNumber   = 8
      ↓
公式に総降水量だと確認
      ↓
units = unknown
      ↓
公式仕様の kg/m² を採用
      ↓
1 kg/m² = 1 mmとして内部単位mmへ

としました。

重要なのは、unknownを一般的に許していないことです。

公式GRIB2キー、

parameterCategory = 1
parameterNumber   = 8

で総降水量と確認できた経路だけに限定しています。

元の情報も、

source_units
source_grib_units
unit_resolution

として属性に残します。


10. DIASの3ファイルを1つのForecastRunへまとめる

修正後、実データを取り込みます。

docker compose run --rm step1 \
  rainband-step1 import-dias-dir

実際の結果は、

run_id   run_20260612T2100Z
shape    (39, 505, 481)
manifest /workspace/data/manifests/dias_msm_archive/run_20260612T2100Z.json

init_time_utc=2026-06-12T21:00:00+00:00
segments=3

となりました。

つまり、

FH00-15
FH16-33
FH34-39
     ↓
1つのForecastRun
     ↓
time × latitude × longitude
     ↓
39 × 505 × 481

へまとめられています。


11. DIAS側もmanifestから再検証する

最後に、DIAS側もZarrと元ファイルを再検証します。

docker compose run --rm step1 \
  rainband-step1 verify \
  --manifest \
  /workspace/data/manifests/dias_msm_archive/run_20260612T2100Z.json

結果は、

run_id    run_20260612T2100Z
raw_files 3
grid      /workspace/data/processed/grid/dias_msm_archive/run_20260612T2100Z.zarr
manifest  /workspace/data/manifests/dias_msm_archive/run_20260612T2100Z.json
status    PASS

でした。

manifestの内容は、

{
  "dimensions": {
    "time": 39,
    "latitude": 505,
    "longitude": 481
  },
  "variables": [
    "precipitation"
  ],
  "time_start": "2026-06-12T22:00:00.000000000",
  "time_end": "2026-06-14T12:00:00.000000000",
  "time_interval_hours": [
    1.0
  ],
  "precipitation_min_mm": 0.0,
  "precipitation_max_mm": 107.0,
  "nan_count": 0
}

となっています。

初期時刻は21:00 UTCですが、1時間降水量の最初の有効時刻は22:00 UTCです。

これは、

22:00の値
= 21:00~22:00の1時間降水量

として扱うためです。

この時間の持ち方は、次のStep 2で3時間積算雨量を作るときに重要となります。


12. 最終的に確認できたこと

Step 1では、最終的に次をすべて実データで確認できました。

項目 結果
Docker build PASS
Ruff format PASS
Ruff check PASS
mypy PASS
pytest PASS
source contract PASS
package source check PASS
Open-Meteo JMA MSM実通信 PASS
Open-Meteo ForecastRun生成 PASS
Open-Meteo Zarr保存・再読込 PASS
Open-Meteo manifest verify PASS
DIAS公式download.py PASS
DIAS MSM Lsurf 3ファイル取得 PASS
DIAS分割ファイル認識 PASS
実MSM GRIB2読込 PASS
GRIB2公式キーから降水量抽出 PASS
units=unknownの安全な解決 PASS
3ファイルを1 ForecastRunへ結合 PASS
DIAS Zarr保存・再読込 PASS
DIAS manifest verify PASS

これでStep 1は完了です。


13. Step 1で作ったデータの境界

ここで大事なのは、Step 1ではまだ「線状降水帯らしい雨域かどうか」を判定していないことです。

Step 1は、

外部の数値予報データ
          ↓
信頼できる共通データ

までです。

最終的にStep 2へ渡すのは、

ForecastRun
├─ time
├─ latitude
├─ longitude
└─ precipitation [mm]

です。

Open-MeteoでもDIASでも、この形にそろえます。

この形を作っておけば、将来LFMやLEPSへ切り替えても、Step 2以降の処理を大きく作り直さずに済みます。


14. r001全体ではまだStep 1

今回完成したのは、r001全体のStep 1です。

r001
│
├─ Step 1  データ取得と共通形式      COMPLETE
│
├─ Step 2  3時間積算雨量
│
├─ Step 3  強雨域候補抽出
│
├─ Step 4  雨域追跡
│
├─ Step 5  複数予測比較
│
└─ Step 6  過去事例検証・地図表示

Step 6まで通したところで、MSMを使ったPrototype r001を完成とします。

r002以降は、

r002  LFM追加
r003  LEPS追加
r004  確率校正
r005  機械学習による補正

という流れを予定しています。


15. 次は3時間積算雨量

Step 2では、今回作ったForecastRunだけを入力にします。

20260915_s03.jpg

まずNumPyで実装します。

処理は、

22:00
23:00
00:00

の3つの1時間降水量を足して、

21:00~00:00

の3時間積算雨量を作るところから始めます。

ここでも、欠測を0 mmとして埋める処理にはしません。

1時間間隔が連続していることを確認してから積算します。

Numbaを使うかどうかは、実データでNumPy版の処理時間を測った後に判断します。


まとめ

Step 1は、見た目には「データを読むだけ」の工程ですが、実データを通してみると、

  • APIごとのデータ構造の違い
  • GRIB2の分割ファイル
  • cfgribが返す変数名
  • GRIB2のパラメータ番号
  • 単位文字列unknown
  • 時刻の定義
  • 保存後の再現性
  • Docker内の権限
  • Ruffやmypyを含む配布物検査

など、先に片付けておくべき点がかなりありました。

ここをStep 1で整理したことで、Step 2以降は、

どこから取ったデータか

ではなく、

ForecastRunの降水量をどう解析するか

に集中できます。

次は、この1時間降水量から3時間積算雨量を作ります。


参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?