コンテンツにスキップ
コンソール

風向・風速

全球の地上 10 m の風の予報です。出典は NOAA GFS と ECMWF オープンデータ(0.25° の緯度経度格子)です。これは数値予報であり、観測ではありません。 形は 2 つあります。色を付けた風速タイル(WebP)と、u/v 成分を含むバイナリ(WND1)です。WND1 は、クライアント側でパーティクルアニメーションを描けます。

モデル model 時間間隔 予報の長さ
gfs 0–120 時間は毎時、その後は 3 時間ごと 最長 384 時間
ecmwf 00、12 UTC の初期値:0–144 時間は 3 時間ごと、その後は 6 時間ごと。06、18 UTC の初期値:3 時間ごと 00、12 UTC は 240 時間まで。06、18 UTC は 90 時間まで

各有効時刻には、最新の 1 回のモデル計算(cycle)の結果だけを残します。有効時刻を過ぎたデータは約 48 時間保持します。

共通のルール(座標、キャッシュ、出典表示、完全な地図の例)は 地図タイルの概要 を参照してください。

  • 推奨表記データ出典:NOAA GFS、ExpTech Studio(exptech.dev)

  • 推奨表記データ出典:ECMWF オープンデータ(CC BY 4.0)、ExpTech Studio(exptech.dev)

上流の条件
  • NOAA GFS:米国政府のデータで、原則として利用制限はありません。NOAA が製品を推奨・保証しているように示さないでください。
  • ECMWF オープンデータ:ECMWF オープンデータは CC BY 4.0 です。ECMWF とライセンス名を表示し、データが加工されていることを示してください。

表記の場所、例、商用利用の申請はデータの利用条件と出典表記をご覧ください。

現在は API Token は不要です。

GEThttps://api.core-tnn1.exptech.dev/api/v2/tiles/wind/{model}/list
サーバーTNN-1
パスパラメーター
  • modelstring必須

    gfs または ecmwf。

リクエスト例
curl --compressed "https://api.core-tnn1.exptech.dev/api/v2/tiles/wind/<model>/list"
レスポンス

成功(データがないときは [ ])。クライアントが対応しているときは gzip で圧縮

[
{
"t": 1794603600,
"c": 1794592800
},
{
"t": 1794614400,
"c": 1794614400
},
{
"t": 1794625200,
"c": 1794614400
}
]

model が gfs でも ecmwf でもないtext/html

サーバーエラー(プレーンテキストのエラーメッセージ)text/plain

試してみる
パスパラメーター

トークンはこのブラウザから API へ直接送信されます。

有効時刻の古い順です。リストには、過ぎた時刻と将来の予報時刻の両方が入っています。

フィールド型説明
[].tinteger有効時刻(この風が表す時刻)です。Unix 秒。
[].cintegerこの予報を出したモデルの初期時刻(cycle)です。Unix 秒。タイルと WND1 の URL を組むときに必須です。
GEThttps://static.core-tnn1.exptech.dev/api/v2/tiles/wind/{model}/{cycle}/{time}/{z}/{x}/{y}.webp
サーバーTNN-1
パスパラメーター
  • modelstring必須

    gfs または ecmwf。

  • cycleinteger必須

    リストのその件の c(Unix 秒)です。現在保持している初期時刻と違うときは 404 です。

  • timeinteger必須

    リストのその件の t(Unix 秒)です。

  • zinteger必須

    ズームレベル。0–11。範囲外は 1 × 1 の透明 GIF を返します。

  • xinteger必須

    XYZ タイルの列番号です。

  • yinteger必須

    XYZ タイルの行番号です(北から南)。

リクエスト例
curl -o output.webp "https://static.core-tnn1.exptech.dev/api/v2/tiles/wind/<model>/<cycle>/<time>/<z>/<x>/<y>.webp"
レスポンス

image/webp、256 × 256、全球、半透明(風が強いほど不透明)、非可逆圧縮。Cache-Control: public, max-age=1800 を付けます。z が 0–11 の外のときは、1 × 1 の透明 GIF(image/gif)を返しますbinary image/webp

If-None-Match が ETag と一致。内容なしレスポンスボディなし

cycle または time が 0

bad cycle

その時刻がない、または cycle がより新しいモデル計算に置き換わった(application/json。内容からはどちらとも区別できません)

{
"error": "Data not found for specified timestamp"
}

サーバーエラーレスポンスボディなし

試してみる
パスパラメーター

トークンはこのブラウザから API へ直接送信されます。

風速 0–32 m/s です。32 m/s を超えるときは 32 の色です。色は区切りの間を線形補間します。

風速 (m/s) 色
0 #282C6E
2 #3056A8
4 #348CBA
6 #40B296
8 #68C45E
11 #B0D048
14 #E2BE4A
17 #E28C42
21 #D6543C
25 #BA3478
28 #9E3CB4
32 #EED6FF

風のパーティクルアニメーションを自分で描くとき、または地点の風向・風速を取るときは、このエンドポイントで格子全体の u/v 成分をダウンロードします。

GEThttps://api.core-tnn1.exptech.dev/api/v1/wind/{model}/list
サーバーTNN-1
パスパラメーター
  • modelstring必須

    gfs または ecmwf。

リクエスト例
curl --compressed "https://api.core-tnn1.exptech.dev/api/v1/wind/<model>/list"
レスポンス

成功(データがないときは [ ])。クライアントが対応しているときは gzip で圧縮

[
{
"t": 1794614400,
"c": 1794614400
},
{
"t": 1794625200,
"c": 1794614400
}
]

model が gfs でも ecmwf でもないtext/html

サーバーエラー(プレーンテキストのエラーメッセージ)text/plain

試してみる
パスパラメーター

トークンはこのブラウザから API へ直接送信されます。

レスポンス形式は /api/v2/tiles/wind/{model}/list と完全に同じです(t、c)。どちらでも使えます。

GEThttps://static.core-tnn1.exptech.dev/api/v1/wind/{model}/{cycle}/{time}.bin
サーバーTNN-1
パスパラメーター
  • modelstring必須

    gfs または ecmwf。

  • cycleinteger必須

    リストのその件の c(Unix 秒)です。現在保持している初期時刻と違うときは 404 です。

  • timeinteger必須

    リストのその件の t(Unix 秒)です。

リクエスト例
curl -o output.bin --compressed "https://static.core-tnn1.exptech.dev/api/v1/wind/<model>/<cycle>/<time>.bin"
レスポンス

application/octet-stream、WND1 形式。Cache-Control: public, max-age=1800 を付けます。クライアントが対応しているときは gzip で転送します(Content-Encoding: gzip)。ブラウザの fetch は自動で展開しますbinary WND1

If-None-Match が ETag と一致。内容なしレスポンスボディなし

その時刻がない、または cycle がより新しいモデル計算に置き換わった

{
"error": "Data not found for specified timestamp"
}

サーバーエラー(プレーンテキストのエラーメッセージ)text/plain

試してみる
パスパラメーター

トークンはこのブラウザから API へ直接送信されます。

すべて little-endian です。固定ヘッダは 67 bytes で、その後にモデル名、続いて u と v の 2 つの面が続きます。

オフセット(bytes) 型 フィールド 説明
0 4 bytes ASCII magic 固定で WND1
4 uint16 version 現在は 1
6 uint16 width 格子の列数 W
8 uint16 height 格子の行数 H
10 float64 lat0 0 行目の緯度(度)
18 float64 lon0 0 列目の経度(度)
26 float64 dlat 行ごとの緯度間隔(度、符号付き。負なら北から南)
34 float64 dlon 列ごとの経度間隔(度、符号付き)
42 float32 uMin u 成分の量子化の下限(m/s)
46 float32 uMax u 成分の量子化の上限(m/s)
50 float32 vMin v 成分の量子化の下限(m/s)
54 float32 vMax v 成分の量子化の上限(m/s)
58 uint64 time 有効時刻。Unix 秒
66 uint8 nameLen モデル名の長さ N
67 N bytes ASCII model gfs または ecmwf
67 + N uint8 × W × H u 東西成分(東向きが正)。行ごとに並びます
67 + N + W × H uint8 × W × H v 南北成分(北向きが正)。行ごとに並びます
  • 値の復元:u = uMin + byte × (uMax − uMin) / 255。v も同様です。単位は m/s です。上下限はこの風データ自身の最小値と最大値で、ファイルごとに違います。
  • 格子の位置:j 行、i 列のオフセットは j × W + i です。位置は緯度 lat0 + j × dlat、経度 lon0 + i × dlon です。経度は ±180° をまたぐので、必要なら換算してください。
  • 欠測:元データの欠測は 0 に量子化されます(つまり下限です)。本当の最小値とは区別できません。風データ全体が欠測のときは、上下限とも 0 です。
  • 格子の大きさ、起点、間隔は、必ずヘッダから読んでください。固定値にしないでください。

MapLibre GL JS に風速レイヤーを追加

Section titled “MapLibre GL JS に風速レイヤーを追加”

リストのうち、有効時刻が現在に最も近い 1 件を取り、その c と t でタイル URL を組み、モデルに合わせて出典を設定します。

const API = "https://api.core-tnn1.exptech.dev";
const STATIC = "https://static.core-tnn1.exptech.dev";
async function addWind(map, model = "gfs") {
const frames = await (await fetch(`${API}/api/v2/tiles/wind/${model}/list`)).json();
if (frames.length === 0) return null;
const now = Date.now() / 1000;
let best = frames[0];
for (const f of frames) {
if (Math.abs(f.t - now) < Math.abs(best.t - now)) best = f;
}
map.addSource("wind", {
type: "raster",
tiles: [`${STATIC}/api/v2/tiles/wind/${model}/${best.c}/${best.t}/{z}/{x}/{y}.webp`],
tileSize: 256,
minzoom: 0,
maxzoom: 11,
attribution: model === "gfs"
? "データ出典:NOAA GFS、ExpTech Studio(exptech.dev)"
: "データ出典:ECMWF オープンデータ(CC BY 4.0)、ExpTech Studio(exptech.dev)",
});
map.addLayer({ id: "wind", type: "raster", source: "wind" });
return best;
}

WND1 をデコードして地点の風を調べる

Section titled “WND1 をデコードして地点の風を調べる”

上の WND1 形式に従って格子全体をデコードし、最も近い格子点の風向と風速を取ります。

const STATIC = "https://static.core-tnn1.exptech.dev";
function decodeWnd1(buffer) {
const dv = new DataView(buffer);
const magic = String.fromCharCode(dv.getUint8(0), dv.getUint8(1), dv.getUint8(2), dv.getUint8(3));
if (magic !== "WND1") throw new Error("not a WND1 file");
const version = dv.getUint16(4, true);
if (version !== 1) throw new Error(`unsupported WND1 version ${version}`);
const width = dv.getUint16(6, true);
const height = dv.getUint16(8, true);
const lat0 = dv.getFloat64(10, true);
const lon0 = dv.getFloat64(18, true);
const dlat = dv.getFloat64(26, true);
const dlon = dv.getFloat64(34, true);
const uMin = dv.getFloat32(42, true);
const uMax = dv.getFloat32(46, true);
const vMin = dv.getFloat32(50, true);
const vMax = dv.getFloat32(54, true);
const time = Number(dv.getBigUint64(58, true));
const nameLen = dv.getUint8(66);
const model = new TextDecoder().decode(new Uint8Array(buffer, 67, nameLen));
const n = width * height;
const start = 67 + nameLen;
const uBytes = new Uint8Array(buffer, start, n);
const vBytes = new Uint8Array(buffer, start + n, n);
const u = new Float32Array(n);
const v = new Float32Array(n);
const uScale = (uMax - uMin) / 255;
const vScale = (vMax - vMin) / 255;
for (let k = 0; k < n; k++) {
u[k] = uMin + uBytes[k] * uScale;
v[k] = vMin + vBytes[k] * vScale;
}
return { model, time, width, height, lat0, lon0, dlat, dlon, u, v };
}
// 最も近い格子点の風(u は東向き、v は北向き、m/s)
function windAt(field, lat, lon) {
const j = Math.round((lat - field.lat0) / field.dlat);
const wrap = Math.abs(360 / field.dlon);
let i = Math.round((lon - field.lon0) / field.dlon);
i = ((i % wrap) + wrap) % wrap;
if (j < 0 || j >= field.height || i >= field.width) return null;
const k = j * field.width + i;
const u = field.u[k];
const v = field.v[k];
const speed = Math.hypot(u, v);
const from = (Math.atan2(-u, -v) * 180 / Math.PI + 360) % 360; // 風の吹いてくる向き。0° が北風
return { u, v, speed, from };
}
const res = await fetch(`${STATIC}/api/v1/wind/gfs/1794614400/1794625200.bin`);
const field = decodeWnd1(await res.arrayBuffer());
console.log(windAt(field, 25.03, 121.56));
  • cycle は変わります。 新しいモデル計算が出ると、同じ有効時刻は新しい cycle で提供され、古い cycle のタイルと WND1 の URL は 404 になります(約 10 秒以内に再試行しても 404 のままです)。404 を受けたらリストを取り直し、新しい c で URL を組んでください。
  • リストにある t と c だけを使ってください。どちらも Unix 秒です。
  • model が gfs でも ecmwf でもないとき、またはパスが数値でないときは 404 です。
  • 同じ URL の内容は固定なので、クライアントで長期にキャッシュできます。
  • GFS と ECMWF は時間分解能と予報の長さが違うため、そのまま置き換えられません。比べるときは、同じ有効時刻を使ってください。
  • 地図には、使ったモデルに合わせて、データライセンスの推奨表記で出典を表示してください。