前回の記事《雪:更快速更流畅的气象数据动画》(中国語)では、予報変数を単一ファイルのコンテナ .xue にまとめ、ブラウザがインデックスを使ってフレームごとに読み込んでいた。
GFS の同じ時次の 2 変数について、フレームごとに着色済みの PMTiles(zoom 0–4)は合計 3,205.87 MB、元の GRIB2 は 137.73 MB、
.xue では約 65 MB まで小さくなった。サイズの問題は解決した。独自コンテナの問題は、自前のデコーダでしか読めないことにある。
2026-09-14、このフォーマットを Pangeo のフォーラムに投稿した。最初の返信は Tom Nicholas からで、質問はこうだった:性能を生んでいるのはこのバイナリフォーマットではなく、可視化に向いた Zarr の chunk 形状を選び、ブラウザ側で WASM デコードすれば足りるのではないか。
答えはその通りである。高速化は二つの要素から来ている。uint8 の単一バイト平面への量子化(圧縮前で 4 分の 1、デコード結果をそのまま WebGL2 の R8 テクスチャとしてアップロードできる)と、再生に合わせて切った chunk である。どちらも標準の Zarr v3 で表現できる。
2026-09-15 から .xue はオンラインでの公開をやめた。すべての時次、ローリングウィンドウの各ラウンド、過去事例はすべて Zarr v3 store のみで公開し、静的な STAC をデータの入口としている。
.xue の元のエンコード・デコードのコードは残してあり、今後エンコード方式を改良する際の参考にする。
現在のパイプラインは、データの流れに沿って次の段階に分かれる:
- 生成:GitHub Actions が定期的に上流データを取得し、量子化して Zarr store にエンコードし、公開する;
- カタログ:静的な STAC カタログが各データソースの現在の時次を列挙する;
- 配信:Cloudflare R2 にデータを置き、CDN がキャッシュし、クライアントは HTTP Range で必要な分を読む;
- 描画:ブラウザ内の Rust WebAssembly がデコードし、WebGL2 が投影と着色を行う;
- 分析:Python スクリプトが store を直接読み、synoptic-analysis Skill が天気概況レポートを生成する。
データソース
| ソース | 格子と間隔 |
|---|---|
| NOAA GFS | 0.25°、F120 まで毎時、以降 F240 まで 3 時間ごと |
| NOAA GFS Sflux | 約 13 km のガウス格子、時間軸は GFS と同じ |
| ECMWF IFS オープンデータ | 0.25°、F144 まで 3 時間ごと、F240 まで 6 時間ごと |
| ECMWF AIFS Single | 0.25°、F360 まで 6 時間ごと |
| ECMWF IFS HRES(Open-Meteo 経由) | 0.1°、F090 まで毎時、F144 まで 3 時間、F360 まで 6 時間、00Z と 12Z のみ |
| NOAA HRRR | 0.03° 米国本土、F18 まで毎時 |
| NOAA GEFS-Aerosols | 0.25°、F120 まで 3 時間ごと |
| NCEP CFSv2 | 地上 0.9375°、上空 1°、F6552(39 週)まで 6 時間ごと、00Z と 12Z のみ |
| NOAA MRMS レーダー | 0.02° 米国本土、2 分ごと、直近 4 時間 |
| 気象庁 高解像度降水ナウキャスト | 0.005° 日本、5 分ごと、直近 3 時間 |
| CMA レーダー合成図 | 0.0439° 中国、6 分ごと、直近 3 時間 |
| Himawari-9 | 0.04°、10 分ごと、直近 6 時間 |
| GOES-19 / GOES-18 | 0.04°、10 分ごと、直近 6 時間 |
| Meteosat-12 | 0.04°、毎時、直近 24 時間 |
ほかに地点プロダクトが三つある:高層観測(1 地点 1 回の放球)、空港(METAR と TAF)、熱帯低気圧(嵐ごとの経路と各機関・各モデルの予報)。地点プロダクトは JSON で、地点または嵐ごとに Range で 1 行を取得し、ラスターのフォーマットには入らない。
データソースは二種類に分かれる。予報ソースは初期時刻ごとに公開され、1 時次 1 ディレクトリで、新しい時次が届くと古いものと置き換わる。観測ソース(レーダーと静止衛星)はローリングウィンドウで、数分ごとに 1 ラウンドをビルドし、ウィンドウを前に進め、取得済みのフレームは再ダウンロードしない。最初から最後まで同じ間隔のソースはないため、時間軸は単位と各フレームのオフセットを直接宣言し、等間隔を前提にしない。
ここでいうリアルタイムは、継続的に更新される予報と準リアルタイムの観測を指す:予報は初期時刻の 4–8 時間後に揃い、観測ウィンドウの末尾は上流の公開遅延に応じて、実時間から数分から 30 分ほど遅れる。
ソースごとの違いは取得段階で吸収する。静止衛星は静止衛星投影から緯度経度格子へ、IFS HRES は縮約ガウス格子から、HRRR はランベルト正角円錐図法の格子から、いずれも取得側で一度だけ再投影・再サンプリングする。その後の量子化、グループ化、store の書き出しはすべてのソースで同じ経路を通る。
MRMS を例にとると、プロダクトは 2 分ごとに 1 フレームを出すが、レーダーのボリュームスキャンは 1 周 4–6 分かかるため、新しい低仰角スキャンが届くまで、そのレーダーの範囲内では前のフレームが値ごとそのまま繰り返される。見た目としては、フレームレートは高いのに合成図上のエコーの動きが途切れ途切れになる。エコーの色の塊は全体としては同じ方向に動いているが、細部ではばらばらに動き、飛び飛びに見える。
フォーマットと量子化
アクセスのパターンは、全球予報 1 時次分の連続再生とタイムラインの自由なシークであり、この制約がフォーマットの選択を決めている。
各変数は 1 バイトに量子化する:2 m 気温は −60 から 50 °C を 0.5 °C 刻みで表し、最大誤差は 0.25 °C;降水は対数のコードブックで上限 128 mm/h とし、弱い雨の段階を保つ。量子化後もデータは数値としての意味を保つので、ブラウザは着色して再生するだけでなく、格子点の値を読んだりモデルを比較したりできる。
データはモデルの元の格子のまま置き、再投影も事前着色もしない。投影とカラーパレットは GPU に任せる。時間方向は 6 フレームで 1 グループとし、なめらかな場はグループ内でフレーム間差分をとり、降水は元の値のまま保存する。降水域は気象システムとともに移動するため、フレーム間差分のほうがかえって大きくなるからである。空間方向はタイルに切り、タイルの大きさはソースごとに設定する(GFS と ECMWF は 52 × 48)。ブラウザはビューポートが覆うタイルだけを取得する。
レイアウト
1 つの bundle は 1 つの Zarr group で、その中に変数ごとに uint8 の配列が 1 つずつあり、形状は [フレーム, 緯度, 経度]。ほかに時間・緯度・経度の三つの座標配列がある。内側の chunk は 6 フレーム × 1 タイルで、chunk ごとに独立した Zstandard フレームになっている。
配列ごとに shard は 1 つだけで、時間軸全体と格子全体を覆うため、1 つの配列がバケット上の 1 オブジェクトになる。スカラーの store は、フレーム数によらず 9 オブジェクトで、GFS の 1 時次は約 1,200 オブジェクトである。 shard のインデックスはオブジェクトの末尾にあり、クライアントは HTTP のサフィックス range 1 回でそれを取得し、以降のすべてのフレームとビューポートはそのインデックスで位置を特定する。
公開する codec チェーンは標準の [bytes, zstd] で、どの Zarr クライアントもプラグインなしで読める。
store には bundle のメタデータと CF の scale_factor / add_offset が含まれ、xarray.open_zarr で読むとそのまま物理量になる。
カタログ
各時次で manifest.json を書き、その横に派生した STAC 1.1.0 カタログを置く:ルートの Catalog の下に、データソースごとに 1 つの Collection、時次ごとに 1 つの Item、ローリングウィンドウはラウンドごとに 1 つの Item、地点プロダクトも同じ三層構成になっている。カタログは manifest とソースのレジストリから生成し、タイムスタンプもホスト名も含まないので、どの文書もディスク上の成果物から再生成できる。リンクはすべて相対パスである。
バケットには各ソースの最新時次だけを残すため、各 Collection には現在の時次を指す固定パスの item.json がある。
読む側では既存のツールを使う:pystac がカタログをたどり、xpystac / odc-stac が Item の Zarr アセットを xarray で開く。
group の文書にはインラインの consolidated metadata が入っているので、ディレクトリ一覧を取得できない素の HTTP バケットでも xr.open_zarr(url) でそのまま開ける。
配信
サイトは Cloudflare Pages 上の静的ページで、データは公開 R2 バケットにあり、間に CDN がある。API 層はない。 R2 の送信トラフィックもエッジでのヒットも無料なので、リクエスト数の多寡は主に遅延に影響し、請求額にはほとんど影響しない。
クライアントは必要な chunk を HTTP Range で読み、同じオブジェクト内の隣接する range は 1 回のリクエストにまとめる。全球表示の 1 フレームは時間ブロックごとに 1 リクエスト、ビューポートはタイル行ごとに 1 リクエスト、1 格子点の時系列全体は時間ブロックごとに 1 chunk である。再生位置の前後のフレームは、バイト数で予算を決めたキャッシュに先読みする。
新しい時次を公開する前にまず CDN をウォームアップし、その後でポインタを切り替える。最初の訪問者もエッジキャッシュから配信される。
計算はビルド段階に集中させ、オンラインには静的ファイルしかない。空間集計や時次をまたぐ連結といった計算は、エンコーダがあらかじめ成果物として計算しておく。
描画
ブラウザ内では Rust WebAssembly の worker が必要に応じて chunk をデコードし、
WebGL2 のフラグメントシェーダがピクセルごとに逆 Web Mercator 投影を行ってからカラーパレットを引く。タイルは格子空間の矩形なので、平面へ戻すのは texSubImage2D 1 回で済み、まだ届いていないタイルはシェーダ内でスキップする。隣接する 2 フレームは GPU 上でブレンドされ、再生時の遷移は連続的になる。
等値線は同じシェーダ内で描き、2 回目の描画パスは要らない。風は GPU のパーティクルレイヤーで描く。オプションとして WebCodecs の H.264 経路(?use_h264=true)があり、同じコード値を可逆圧縮の動画で運ぶため、ファイルが小さくなる。
分析
データはオープンな CORS の静的ファイルで、キーは不要であり、ラップされた API を経由せずに読める。リポジトリには synoptic-analysis Skill が含まれており、 Python を実行できる AI アシスタントと組み合わせると、STAC カタログから実データを読み、天気概況を分析し、レポートを書く。例えば次のように依頼できる:
東京の今後 3 日間の天気概況を分析し、GFS と ECMWF の降水と風の予報を比較し、最新の衛星、レーダー、高層観測のデータも合わせて、図、データの時刻、不確実性の説明を含む日本語のレポートを作成してください。
Skill の作業手順は、領域と有効時刻を決め、モデルの場を読み、上層のトラフとリッジ、ジェット気流、地上の気圧系、水蒸気輸送、降水の条件を分析し、観測でモデルの挙動を確かめ、モデル間の違いを比較する、というものである。 1 回の分析で使うデータはまずローカルに保存し、それを計算、作図、検証に使うので、レポート内の数値と図は同じデータに基づく。対象領域、分析の重点、レポートの言語、出力形式はいずれも変更できる。
実際に作成したレポートの例として、台風 Dujuan が東京に影響を及ぼす直前の解析がある:東京都台風気象過程解析(PDF)。作成の経緯は《LLM による複数データソースの気象解析》を参照。
セルフホスティング
独立して運用する場合は、上流のオープンデータを選び、対応する処理パイプラインを動かす。ローカルでは 1 つのコマンドで 1 時次をビルドできる:
| |
依存関係は Python、GRIB ドライバ付きの GDAL、Node.js で、WASM デコーダのビルドには Rust ツールチェーンが要る。フロントエンドのデータの URL、R2 バケット、パスはいずれも設定できる。別のオブジェクトストレージや社内ネットワークへ移す場合は、公開フロー、ネットワークアクセス、権限設定を変更する必要があるが、フォーマット、カタログ、デコーダは変わらない。
デプロイ後に調整できる範囲には、データ(1 つのモデルといくつかのよく使う変数から始め、必要に応じて広げる)、画面(配色、レイヤー、特定の地域向けの構成)、分析(Skill の分析ロジックとレポート形式)がある。
コードは Apache 2.0 OR MIT のデュアルライセンスで、どちらか一方を選べる。いずれも商用利用、改変、クローズドソースでの再配布を認め、著作権表示とライセンス表示の保持だけを求める。データのライセンスはソースごとに異なる:NOAA のプロダクトはパブリックドメイン、ECMWF のオープンデータと Open-Meteo 経由の IFS HRES は CC BY 4.0 でクレジット表示が必要、ベースマップは © OpenStreetMap contributors。
- オンラインデモ:xue.ringsaturn.me
- ソースコード:github.com/ringsaturn/xue
- データカタログ:dataset.ringsaturn.me/xue