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 ー MSMの取得から始めて、LFM・LEPSへ拡張できるデータ基盤を作る

0
Last updated at Posted at 2026-09-12

線状降水帯予測プロトタイプを作ってみる【r001】Step 1 ー MSMの取得から始めて、LFM・LEPSへ拡張できるデータ基盤を作る

はじめに

前回の記事にも書いていますが、線状降水帯の予測方法を試すとしても、最初から雨域の判定や機械学習へ進むのは少し早いと考えています。

まず固めたいのは、数値予報データを取得し、データ元が違ってもStep 2以降で同じように扱える形へそろえることです。

今回のプロトタイプは、無償で試せるMSMから始めます。その後、検証が進めば、より高解像度のLFMやアンサンブル予報のLEPSを使う段階へ進む予定です。そこで問題になるのが、データの配信方法やファイル形式の違いです。

MSMからLFM、LEPSへ移るたびに、3時間積算雨量や強雨域抽出の処理まで書き直す構成にはしたくありません。

Step 1では、データ取得方法の違いを吸収するために**データ取得アダプター(Provider)を用意し、どのデータを使っても、最後は共通予報データ(ForecastRun)**としてStep 2へ渡せる形にします。

この記事では、次のところまで実装します。

数値予報データ
   ↓
データ取得アダプター(Provider)
   ↓
取得・形式確認
   ↓
単位・時刻・格子をそろえる
   ↓
共通予報データ(ForecastRun)
   ↓
元データ保存 / 格子データ保存 / 処理記録
   ↓
保存結果を読み直して確認

Step 1では、まだ「線状降水帯が発生する」といった判定はしません。


今回の位置づけ

全体は次の6段階で進める予定です。

ChatGPT Image 2026年9月9日 15_52_10.png

今回扱うのは一番左側だけです。

ここを丁寧に作っておけば、後から

MSM
 ↓
LFM
 ↓
LEPS

とデータを変えても、Step 2以降をそのまま使いやすくなります。


まずは無償で使えるデータから始める

r001では、まず次の2つをデータ源として使います。

データ取得方法 用途 Step 1での扱い
Open-Meteo JMA MSM 最新予報や、初期時刻を指定したMSMの動作確認 APIから取得
DIAS GPV Archive 過去のMSMを使った再現・検証 正規手順で取得済みのGRIB2を読込

Open-MeteoのJMA APIでは、JMA MSMが約5 km、1時間間隔で提供されています。複数地点を1回のリクエストで指定でき、cell_selection=nearestも指定できます。降水量はmm単位で、時間値は直前1時間の積算です。

また、Single Runs APIを使うと、予報計算を開始した時刻(初期時刻)を指定して、その時点の予報結果を取得できます。Open-Meteoの説明では、JMA MSMを含む多くのモデルについて2026年4月2日以降の予報計算が対象です。

一方、DIASのGPV Data Archiveには気象庁のGPVが保存されており、DIASのWeb画面や公式ダウンロードスクリプトを使って実ファイルを取得できます。今回のStep 1では、その正規手順で取得したMSMのGRIB2を読み込むところまでを実装し、URLを推測してファイルを自動巡回する処理は作っていません。

ここは便利さよりも、利用条件を守れる構成を優先しました。

Open-Meteoの無料API、DIASともに利用条件があります。今回のr001は非商用の技術検証を前提としています。業務利用・商用利用へ進む場合は、その時点の利用条件を確認します。


1. データ取得方法の違いをデータ取得アダプターで吸収する

今回の中心は、データ取得方法の違いを受け持つデータ取得アダプターです。コード上ではForecastProviderという名前にしています。

class ForecastProvider(ABC):
    """数値予報データ取得元の共通インターフェース。"""

    provider_id: str
    model_id: str

    @abstractmethod
    def fetch(
        self,
        *,
        grid: GridSpec | None = None,
        run_time_utc: datetime | None = None,
        **kwargs: Any,
    ) -> ProviderOutput:
        """外部データを取得し、単位・時刻・格子を共通ルールにそろえて返す。"""
        raise NotImplementedError

外部データがJSONでもGRIB2でも、取得後はProviderOutputという共通の取得結果にそろえます。

ChatGPT Image 2026年9月9日 15_54_39.png

将来LFMやLEPSを使うときは、同じ仕組みに次のProviderを追加します。

JmbscLfmProvider
JmbscLepsProvider
JmaCloudLepsDetailProvider

Step 2以降の処理は、どのProviderから取得したかを意識せず、同じ共通データを受け取ります。


2. Step 2以降へ渡す共通データ形式

Step 1では、Step 2以降へ渡す格子データをxarray.Datasetに統一しました。xarray.Datasetは、時刻・緯度・経度のような座標を持つ多次元データを扱いやすいPythonのデータ構造です。

必須の次元は次の3つです。

time
latitude
longitude

降水量は、

precipitation(time, latitude, longitude)

として持ちます。

降水量の単位はmm、緯度・経度はEPSG:4326、内部で扱う時刻はUTCにそろえます。ここでいう「そろえる」とは、取得元ごとの違いをStep 2以降へ持ち込まないための変換です。

予報計算を開始した時刻(初期時刻)、データを取得した時刻、取得元などはForecastRunMetadataにまとめます。

ForecastRunMetadata
├─ provider_id
├─ model_id
├─ run_id
├─ init_time_utc
├─ retrieved_at_utc
├─ member_id
├─ canonical_crs
├─ canonical_timezone
├─ precipitation_unit
└─ source_notes

この段階で大事なのは、分からない情報を補完しないことです。

例えばOpen-Meteoの通常Forecast APIから最新予報を取得した場合、元の予報計算が何時に開始されたかを、こちらの推測で補いません。その場合はinit_time_utc=Noneとし、取得した時刻とAPIの元レスポンスを保存します。

特定の予報初期時刻を指定して検証したい場合は、Single Runs APIを使います。


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

Open-Meteoは、GRIB2の格子ファイルをそのまま配る仕組みではなく、緯度・経度を指定して予報値を取得するAPIです。

そこで、対象範囲に一定間隔の格子点を作り、その座標をまとめてAPIへ問い合わせます。

ChatGPT Image 2026年9月9日 15_56_28.png

設定例は次のようにしています。

openmeteo:
  forecast_base_url: https://api.open-meteo.com/v1/forecast
  single_run_base_url: https://single-runs-api.open-meteo.com/v1/forecast
  model: jma_msm
  hourly_variables:
    - precipitation
  forecast_hours: 12

  grid:
    west: 137.50
    east: 138.00
    south: 34.60
    north: 35.00
    longitude_step: 0.0625
    latitude_step: 0.05

最初から全国を一度に取得する設定にはしていません。

無料APIで方法を確認する段階なので、まず小さな範囲で動作を確かめます。対象地域を広げる場合は、APIの利用量と取得方式を見直します。

API要求では、少なくとも次を明示します。

models=jma_msm
cell_selection=nearest
elevation=nan
timezone=GMT
precipitation_unit=mm

elevation=nanはOpen-Meteo側の標高補正を無効にするために指定しています。今回の目的は、要求地点へ統計的な地形補正を掛けることではなく、予報格子の値をStep 2以降の解析へ渡すことだからです。


4. DIASの過去MSMを読み込む

過去事例の検証ではDIASを使えるようにしておきます。

ただし、ここでのProviderは、DIAS上のファイルを推測して自動巡回するためのものではありません。

DIAS Web画面 / 公式ダウンロードスクリプト
             ↓
          GRIB2取得
             ↓
      data/incomingへ配置
             ↓
 DiasMsmArchiveProvider

読み込む前に、ファイル名からMSMのデータであることと初期時刻を確認します。

Z__C_RJTD_YYYYMMDDhhmmss_MSM_GPV_Rjp_Lsurf_FH.._grib2.bin

初期時刻はファイル名から読み取ります。

GRIB2はcfgribとecCodesで読み込み、降水量の変数を取り出します。

単位については、分からない単位を推測して変換しません。

現在の実装では、

mm
kg m-2
m

の既知ケースだけを扱います。mなら1000倍、kg m-2は水深mmと同値として扱い、それ以外なら停止します。

この方が、間違った単位のままStep 2へ進むより安全です。


5. 取得した元データを残す

加工後のZarrだけ保存すると、後で結果がおかしかったときに原因を追いにくくなります。

そのため、API応答やGRIB2は取得した元データとして残します。

data/
├─ raw/
│  ├─ openmeteo_jma_msm/
│  └─ dias_msm_archive/
│
├─ processed/
│  └─ grid/
│
└─ manifests/

取得した各元ファイルについて、

path
SHA-256
size_bytes
media_type
source_reference

を処理記録(manifest)へ記録します。manifestは、どのデータを取得し、どこへ保存し、どのSHA-256だったかを後から確認するための記録です。

DIASのGRIB2は、既定では取得した元データの保存領域へコピーします。

大きなファイルを二重保存したくない場合に備えてcopy_raw_file=falseも用意していますが、再現性を優先するなら既定値のまま使う想定です。


6. 共通格子データを保存し、読み直して確認する

データ取得アダプターから返ったxarray.Datasetは、Step 2以降で扱いやすいようZarr形式で保存します。Zarrは、時間方向や格子方向に大きくなる多次元データを分割して保存し、必要な部分を読み出しやすい形式です。

ProviderOutput
      ↓
共通形式になっているか確認
      ↓
Zarrへ保存
      ↓
Zarrを読み直す
      ↓
格子サイズを再確認
      ↓
処理記録(manifest)を保存

「保存できた」で終わらせず、保存した格子データをもう一度開き、同じ内容として読み直せるところまでをStep 1の完了条件にしています。

処理記録として残すmanifestには、例えば次の情報を保存します。

{
  "schema_version": "1.0",
  "metadata": {},
  "raw_assets": [],
  "grid": {
    "path": "...",
    "sha256_tree": "..."
  },
  "dataset_summary": {
    "dimensions": {},
    "time_start": "...",
    "time_end": "...",
    "precipitation_min_mm": 0.0,
    "precipitation_max_mm": 0.0,
    "nan_count": 0
  }
}

Zarrは1ファイルではないので、ファイル単体のSHA-256ではなく、構成ファイルを順番にハッシュしてツリー全体のSHA-256を作っています。


7. Docker環境

開発環境はUbuntu 24.04を基準にしました。

Windows 11
   ↓
WSL2 Ubuntu 24.04
   ↓
Docker Engine + Docker Compose
   ↓
NVIDIA CUDA系 Ubuntu 24.04 image
   ↓
Python / xarray / cfgrib / ecCodes

Dockerfileの先頭は次の形です。

FROM nvidia/cuda:12.8.1-base-ubuntu24.04

PythonパッケージはUbuntu 24.04のsystem Pythonへ直接pip installせず、/opt/venvへ仮想環境を作ります。

これはPEP 668の影響を避け、OS側Pythonとアプリ依存を分けるためです。

また、WSL側へroot所有の出力ファイルを作らないよう、Composeではホスト側UID/GIDを渡します。

user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"

.envは次のように作ります。

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

GPUについて

Step 1の処理はAPI取得、GRIB2読込、単位・時刻・格子の整理、保存が中心なので、GPUを使って高速化する処理ではありません。

それでもCUDA系イメージを使うのは、後のStepで機械学習やGPU処理を追加するときに環境を大きく組み替えないためです。

GPUがないPCではcompose.yamlだけを使います。

GPUを確認する場合だけ、

./scripts/gpu_check.sh

を実行します。

compose.gpu.yamlではgpus: allを追加しています。


8. ディレクトリ構成

実装は次のように分けました。

linear_rainband_r001_step1/
├─ Dockerfile
├─ compose.yaml
├─ compose.gpu.yaml
├─ pyproject.toml
├─ .env.example
│
├─ config/
│  └─ step1.yaml
│
├─ src/rainband_step1/
│  ├─ cli.py
│  ├─ config.py
│  │
│  ├─ domain/
│  │  └─ models.py
│  │
│  ├─ providers/
│  │  ├─ base.py
│  │  ├─ openmeteo_jma_msm.py
│  │  └─ dias_msm_archive.py
│  │
│  ├─ services/
│  │  └─ ingest.py
│  │
│  ├─ storage/
│  │  ├─ raw_store.py
│  │  ├─ grid_store.py
│  │  └─ manifest_store.py
│  │
│  └─ utils/
│     └─ hashing.py
│
├─ tests/
└─ scripts/

データ取得部分と保存処理は、役割を混ぜないために分けています。

LFMやLEPSなど新しい取得先を追加するときは、主にproviders/へコードを追加します。Zarr保存や処理記録の仕組みはそのまま使えます。


9. 実行方法

9.1 ビルド

git clone <repository>
cd linear_rainband_r001_step1

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 compose build

9.2 設定確認

docker compose run --rm step1 \
  rainband-step1 show-config

9.3 最新のOpen-Meteo JMA MSMを取得

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

9.4 初期時刻を固定する

特定の予報計算の初期時刻を指定して取得する場合です。

docker compose run --rm step1 \
  rainband-step1 fetch-openmeteo \
  --config config/step1.yaml \
  --run 2026-09-09T00:00Z

指定した予報計算(Run)がOpen-Meteo側に存在しない場合はAPIエラーにします。時刻を自動で丸めたり、別の予報計算へ勝手に切り替えたりはしません。

9.5 DIASから取得済みGRIB2を読む

例えば、ファイルをdata/incomingへ置きます。

mkdir -p data/incoming

その後、

docker compose run --rm step1 \
  rainband-step1 import-dias \
  --config config/step1.yaml \
  --file /workspace/data/incoming/Z__C_RJTD_20260909000000_MSM_GPV_Rjp_Lsurf_FH00-15_grib2.bin

とします。


10. Step 1を完了とする条件

Step 1が終わったと判断する条件は次です。

  • Open-Meteo JMA MSMを取得できる
  • DIAS取得済みMSM GRIB2を読める
  • 両方を共通予報データ(ForecastRun)へ変換できる
  • 降水量単位がmmにそろう
  • 時刻がUTCにそろう
  • 緯度・経度格子が正しく並ぶ
  • 取得した元データとSHA-256が残る
  • Zarrへ保存できる
  • Zarrを再読込できる
  • 処理記録(manifest)から取得元を追跡できる

単体テストでは大半を確認していますが、実環境でのAPI・GRIB2・Zarr・Docker確認まで終えてStep 1を完了扱いにします。

次回step1の完了するソースを載せる予定です。


11. Step 2では3時間積算雨量を作る

Step 2では、今回そろえた共通予報データForecastRunだけを入力にします。

ChatGPT Image 2026年9月9日 15_58_24.png

Step 2以降は、元データがOpen-MeteoかDIASかを意識せずに処理できます。

これがStep 1で作っておきたい土台です。MSMで始めても、後からLFMやLEPSへ移りやすくなります。


参考資料

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?