はじめに
富山県氷見市の「寒ブリ」は、定置網漁で水揚げされる冬の高級魚として全国的に知られるブランド魚だ。
毎年冬になると「寒ブリ宣言」が発令されるが、その裏には明確な科学的根拠がある。海水温が15℃を下回ると、ブリが南下してくるのだ。
たまたまXを見ていたらJAXAのAPIを使った投稿がポストされていたので、使いたいと思った。「それを富山湾の寒ブリと組み合わせて可視化できないか?」と思ったのがこのアプリの出発点だった。
JAXAが運用するGCOM-C衛星(通称「しきさい」)の海面水温データを使い、富山湾の水温変化を月別にインタラクティブマップで見せるアプリを作った。
👉 デモはこちら: https://himi-ai-lab.jp/apps/027_toyamawan_sst/
使ったデータ・技術
JAXA GCOM-C(しきさい)とは
JAXAが2017年に打ち上げた地球観測衛星。搭載センサー「SGLI」で海面水温(SST)・植物プランクトン量・植生状況などを計測している。
今回使ったデータの仕様:
項目内容データ種別海面水温(SST)月別コンポジット空間分解能約250m対象期間2022〜2024年(月別)利用条件APIキー不要・ユーザー登録不要
登録不要で使えるのが大きなポイント。
技術スタック
JAXA Earth API for JavaScript v2.0.0
HTML5 + Vanilla JS(シングルファイル構成)
CSS3(CSS変数でダークテーマ管理)
フレームワークは使わず、シングルHTMLファイルで完結させた。共有ホスティング環境でも動作するよう意識している。
APIの読み込みはCDNから1行で済む:
html
実装のポイント
- データ取得とCanvas描画
je.getDataObject() にコレクションURL・バンド名・バウンディングボックス・日付を渡すだけでデータが取れる。
javascriptconst SST_COLLECTION = "https://s3.ap-northeast-1.wasabisys.com/je-pds/cog/v1/JAXA.G-Portal_GCOM-C.SGLI_standard.L3-SST.daytime.v3_global_monthly/collection.json";
const TOYAMA_BBOX = [136.65, 36.60, 137.35, 37.10]; // [西, 南, 東, 北]
const dataObject = await je.getDataObject({
collectionUrl: SST_COLLECTION,
band: "SST",
bbox: TOYAMA_BBOX,
width: 500,
height: 375,
date: new Date(${year}-${monthStr}-15T00:00:00Z),
});
// カラーマップ作成してCanvasに描画
const colorMap = new je.image.ColorMap({
min: 5, max: 30, // 5℃〜30℃
colors: je.Colors.JET,
nanColor: "0a0f1eff", // 雲による欠損値は暗い色
});
const tempCanvas = je.image.createCanvas(dataObject, colorMap);
const ctx = canvas.getContext('2d');
ctx.drawImage(tempCanvas, 0, 0);
2. 平均水温の計算と色変化
je.data.globalStat() で統計値が取得でき、水温に応じてHSLカラーで数値表示の色を変化させた。
javascriptconst stat = je.data.globalStat(dataObject);
const avgTemp = stat.mean;
// 水温に応じてHSL色相を変化(青=低温 / 赤=高温)
const ratio = (avgTemp - 5) / (30 - 5);
const hue = (1 - Math.min(1, Math.max(0, ratio))) * 240;
tempEl.style.color = hsl(${hue}, 80%, 60%);
3. 月別スライダーと自動再生
javascript// スライダー操作
document.getElementById('monthSlider').addEventListener('input', function(e) {
currentMonth = parseInt(e.target.value);
loadAndRender(currentYear, currentMonth);
});
// 再生ボタン(2秒ごとに月を進める)
function togglePlay() {
if (isPlaying) {
clearInterval(playInterval);
isPlaying = false;
} else {
isPlaying = true;
playInterval = setInterval(() => {
currentMonth = (currentMonth + 1) % 12;
document.getElementById('monthSlider').value = currentMonth;
loadAndRender(currentYear, currentMonth);
}, 2000);
}
}
4. 年切替(2022〜2024)
javascriptfunction toggleYear() {
const years = [2022, 2023, 2024];
const idx = years.indexOf(currentYear);
currentYear = years[(idx + 1) % years.length];
document.getElementById('yearBtn').textContent = currentYear + '年';
loadAndRender(currentYear, currentMonth);
}
5. 取得データのキャッシュ
月切替のたびにAPIリクエストを飛ばすと遅いので、取得済みデータはオブジェクトにキャッシュして使い回した。
javascriptconst dataCache = {};
async function loadAndRender(year, month) {
const cacheKey = ${year}-${month};
if (dataCache[cacheKey]) {
dataObject = dataCache[cacheKey];
} else {
dataObject = await je.getDataObject({...});
dataCache[cacheKey] = dataObject;
}
}
ハマったところ
APIのレスポンス遅延
データ取得中はローディングオーバーレイを表示する必要があった。classList の hidden クラスで制御している。
javascriptloading.classList.remove('hidden'); // 取得開始時に表示
// ...データ取得処理...
loading.classList.add('hidden'); // 取得完了後に非表示
雲による欠損値
GCOM-Cのデータは雲がある場合に欠損(NaN)が発生する。nanColor を暗い色に設定することで、欠損部分が自然に見えるよう対処した。また stat.mean がNaNになる場合も考慮してガードを入れた。
夏(7〜8月):富山湾は25℃超の赤い海
11月から急激に冷え始める
1〜2月:15℃以下の青い海 → 寒ブリシーズン
スライダーで赤かった海が青に変わっていく様子を見ると、寒ブリが南下してくる理由が感覚的に理解できる。
おわりに
JAXAのデータは無料・登録不要で使えるので、他にも何ができるかを考えたい。今回のように「地元の文化・産業と衛星データをつなぐ」用途は、他の地域にも応用できると感じた。
このアプリは、氷見市でAIとWebアプリを使った地域課題解決を進めている氷見AILabの取り組みの一つだ。人口減少・観光・水産業など地域の課題をテクノロジーで可視化するアプリを100本以上開発している。
👉 https://himi-ai-lab.jp/
参考リンク
JAXA Earth API
デモアプリ
アプリ解説記事
