はじめに
気象データを使ったアプリや可視化を作りたいとき、まず欲しくなるのが「いま各地で観測されている値」です。
気象庁のWebサイトでは、アメダスの観測値がJSON形式で公開されています。
この記事では、次の流れでアメダスの観測値を扱ってみます。
- PythonでアメダスJSONを取得する
- 観測所番号と観測所情報を結合する
- JavaScriptとLeafletで地図上に表示する
最終的には、全国の観測所を地図上にプロットし、気温・1時間降水量・風速で色分けできる簡単な地図を作ります。
今回使うデータ
今回利用するJSONは、気象庁のアメダスページで使われている公開データです。
ただし、気象庁が「API仕様書」として整理して公開しているものではなく、Webページから参照されているJSONを利用する形になります。
利用する場合は、気象庁ホームページの利用条件や出典の書き方も確認しておくと安心です。
主に使うのは次の3つです。
最新の観測時刻
https://www.jma.go.jp/bosai/amedas/data/latest_time.txt
取得可能な最新の観測時刻が返ってきます。
例:
2026-06-27T14:00:00+09:00
この値を使うと、存在する最新データを安全に取得できます。
特定時刻の全国アメダス観測値
https://www.jma.go.jp/bosai/amedas/data/map/{YYYYMMDDHHMMSS}.json
例えば、2026年6月27日14時00分のデータを取得する場合は次のURLになります。
https://www.jma.go.jp/bosai/amedas/data/map/20260627140000.json
このJSONには、全国の観測所の観測値がまとめて入っています。
アメダス観測所一覧
https://www.jma.go.jp/bosai/amedas/const/amedastable.json
観測所番号に対応する観測所名、緯度、経度、標高などが入っています。
観測値のJSONだけを見るとキーが観測所番号になっているため、そのままでは「どこのデータなのか」が分かりにくいです。
そこで、観測値JSONと観測所一覧JSONを観測所番号で結合します。
観測値JSONの構造
map/{YYYYMMDDHHMMSS}.json は、ルートオブジェクトのキーが観測所番号になっています。
一部を抜き出すと、次のような形です。
{
"11001": {
"temp": [12.0, 0],
"precipitation1h": [1.0, 0],
"windDirection": [3, 0],
"wind": [7.0, 0]
},
"11016": {
"temp": [13.2, 0],
"precipitation1h": [0.5, 0],
"windDirection": [3, 0],
"wind": [4.0, 0]
}
}
各観測項目は配列になっており、先頭の要素が観測値です。
この記事では、まず先頭の値だけを取り出して扱います。
主な項目は次の通りです。
| キー | 内容 |
|---|---|
temp |
気温 |
precipitation10m |
10分降水量 |
precipitation1h |
1時間降水量 |
wind |
風速 |
windDirection |
風向 |
humidity |
湿度 |
sun10m |
10分日照時間 |
sun1h |
1時間日照時間 |
観測所によって持っている項目は異なります。
Pythonで取得してみる
まずはPythonでデータを取得し、構造を確認します。
ここでは外部ライブラリを使わず、標準ライブラリだけで書きます。
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from datetime import datetime
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
BASE_URL = "https://www.jma.go.jp/bosai/amedas"
LATEST_TIME_URL = f"{BASE_URL}/data/latest_time.txt"
AMEDAS_TABLE_URL = f"{BASE_URL}/const/amedastable.json"
MAP_URL = f"{BASE_URL}/data/map/{{timestamp}}.json"
def fetch_text(url: str) -> str:
with urlopen(url, timeout=20) as response:
return response.read().decode("utf-8")
def fetch_json(url: str) -> dict:
return json.loads(fetch_text(url))
def latest_timestamp() -> tuple[str, str]:
latest_time = fetch_text(LATEST_TIME_URL).strip()
dt = datetime.fromisoformat(latest_time)
return latest_time, dt.strftime("%Y%m%d%H%M%S")
def degree_minute_to_decimal(value: list[float]) -> float:
degree, minute = value
return degree + minute / 60
def observed_value(record: dict, key: str):
item = record.get(key)
if not item:
return None
return item[0]
ポイントは次の3つです。
-
latest_time.txtから最新時刻を取得する - ISO 8601形式の時刻を
YYYYMMDDHHMMSSに変換する - 観測値配列の先頭要素を取り出す
観測所情報と結合する
観測値JSONのキーは観測所番号です。
そのため、amedastable.json から同じ観測所番号の情報を引きます。
def build_rows(amedas_map: dict, amedas_table: dict) -> list[dict]:
rows = []
for station_id, observation in amedas_map.items():
station = amedas_table.get(station_id, {})
lat = station.get("lat")
lon = station.get("lon")
rows.append(
{
"id": station_id,
"name": station.get("kjName", "(unknown)"),
"lat": degree_minute_to_decimal(lat) if lat else None,
"lon": degree_minute_to_decimal(lon) if lon else None,
"temp": observed_value(observation, "temp"),
"precipitation1h": observed_value(observation, "precipitation1h"),
"wind": observed_value(observation, "wind"),
"windDirection": observed_value(observation, "windDirection"),
}
)
return rows
amedastable.json の緯度・経度は、次のように [度, 分] の形式です。
{
"lat": [45, 31.2],
"lon": [141, 56.1]
}
地図で扱いやすいように、十進度へ変換します。
def degree_minute_to_decimal(value: list[float]) -> float:
degree, minute = value
return degree + minute / 60
実行する
サンプルスクリプトは次のように実行できます。
python3 scripts/fetch_amedas.py --limit 5
特定時刻を指定する場合は --time を使います。
python3 scripts/fetch_amedas.py --time 20260627140000 --limit 5
出力例:
取得URL: https://www.jma.go.jp/bosai/amedas/data/map/20260627140000.json
観測所数: 1286
観測所ID 観測所名 緯度 経度 気温 1時間降水量 風速 風向
--------------------------------------------------------------------------------------
11001 宗谷岬 45.5200 141.9350 14.0℃ 0.0mm 7.1m/s 3
11016 稚内 45.4150 141.6783 14.5℃ 0.0mm 5.9m/s 2
11046 礼文 45.3050 141.0450 14.1℃ 0.0mm 3.6m/s 2
11061 声問 45.4033 141.8017 15.5℃ 0.0mm 5.3m/s 3
11076 浜鬼志別 45.3350 142.1700 12.9℃ 0.5mm 6.0m/s 1
ここまでで、観測値と観測所情報を結合できました。
次は、この緯度・経度を使って地図に表示します。
JavaScriptで地図表示する
ブラウザでは fetch を使って、同じJSONを直接取得できます。
今回は地図表示にLeafletを使います。
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
地図のHTMLは次のような構成にします。
- 表示する観測項目を選ぶセレクトボックス
- 観測時刻を入力するテキストボックス
- 地図を表示する
div
<div class="toolbar">
<select id="metric">
<option value="temp">気温</option>
<option value="precipitation1h">1時間降水量</option>
<option value="wind">風速</option>
</select>
<input id="time" placeholder="例: 20260627140000">
<button id="load">表示</button>
<div id="status" class="status">読み込み前</div>
</div>
<div id="map"></div>
最新時刻を取得する
時刻欄が空の場合は、latest_time.txt から最新時刻を取得します。
const BASE_URL = "https://www.jma.go.jp/bosai/amedas";
const LATEST_TIME_URL = `${BASE_URL}/data/latest_time.txt`;
const AMEDAS_TABLE_URL = `${BASE_URL}/const/amedastable.json`;
const MAP_URL = `${BASE_URL}/data/map`;
function toTimestamp(latestTimeText) {
return latestTimeText.replace(/\D/g, "").slice(0, 14);
}
async function fetchLatestTimestamp() {
const response = await fetch(LATEST_TIME_URL);
const latestTimeText = await response.text();
return toTimestamp(latestTimeText.trim());
}
例えば 2026-06-27T14:00:00+09:00 は、20260627140000 に変換されます。
観測値と観測所一覧を取得する
観測値JSONと観測所一覧JSONは、並列で取得できます。
async function fetchJson(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return response.json();
}
const timestamp = timeInput.value.trim() || await fetchLatestTimestamp();
const [amedasMap, amedasTable] = await Promise.all([
fetchJson(`${MAP_URL}/${timestamp}.json`),
fetchJson(AMEDAS_TABLE_URL),
]);
地図用のデータに変換する
Python版と同じように、観測所番号で結合します。
function toDecimalLatLon(value) {
const [degree, minute] = value;
return degree + minute / 60;
}
function observedValue(observation, key) {
return observation[key]?.[0] ?? null;
}
function buildRows(amedasMap, amedasTable) {
return Object.entries(amedasMap).flatMap(([stationId, observation]) => {
const station = amedasTable[stationId];
if (!station?.lat || !station?.lon) {
return [];
}
return {
id: stationId,
name: station.kjName,
lat: toDecimalLatLon(station.lat),
lon: toDecimalLatLon(station.lon),
temp: observedValue(observation, "temp"),
precipitation1h: observedValue(observation, "precipitation1h"),
wind: observedValue(observation, "wind"),
};
});
}
これで、Leafletに渡せる緯度・経度付きのデータになります。
Leafletでプロットする
地図を初期化します。
const map = L.map("map", { zoomControl: false }).setView([36.5, 138], 5);
const markerLayer = L.layerGroup().addTo(map);
L.control.zoom({ position: "bottomright" }).addTo(map);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 18,
attribution: "© OpenStreetMap contributors",
}).addTo(map);
観測値に応じて色を変えます。
function colorByMetric(metric, value) {
if (metric === "temp") {
if (value >= 30) return "#d73027";
if (value >= 25) return "#fc8d59";
if (value >= 20) return "#fee08b";
if (value >= 10) return "#91bfdb";
return "#4575b4";
}
if (metric === "precipitation1h") {
if (value >= 50) return "#7f0000";
if (value >= 30) return "#d7301f";
if (value >= 10) return "#fc8d59";
if (value > 0) return "#91bfdb";
return "#d9d9d9";
}
if (value >= 20) return "#54278f";
if (value >= 10) return "#756bb1";
if (value >= 5) return "#9e9ac8";
return "#cbc9e2";
}
最後に、観測所ごとに circleMarker を置きます。
function renderMarkers(rows, metric) {
markerLayer.clearLayers();
rows
.filter((row) => row[metric] !== null)
.forEach((row) => {
const value = row[metric];
const marker = L.circleMarker([row.lat, row.lon], {
radius: 5,
color: "#24292f",
fillColor: colorByMetric(metric, value),
fillOpacity: 0.82,
weight: 0.6,
});
marker.bindPopup(`
<strong>${row.name}</strong><br>
観測所ID: ${row.id}<br>
気温: ${row.temp ?? "-"}℃<br>
1時間降水量: ${row.precipitation1h ?? "-"}mm<br>
風速: ${row.wind ?? "-"}m/s
`);
marker.addTo(markerLayer);
});
}
これで、全国のアメダス観測所が地図上に表示されます。
気温を選ぶと、気温の高い地域が暖色、低い地域が寒色で表示されます。
表示例
気温で色分けした例です。
1時間降水量で色分けした例です。
風速で色分けした例です。
サンプルスクリプト
今回作成したサンプルは次の2つです。
scripts/fetch_amedas.py
examples/amedas_map.html
Python版:
python3 scripts/fetch_amedas.py --time 20260627140000 --limit 5
JavaScript版:
examples/amedas_map.html
HTMLファイルをブラウザで開くと、地図が表示されます。
LeafletとOpenStreetMapのタイルをCDNから読み込むため、表示時にはインターネット接続が必要です。
注意点
存在しない時刻を指定しない
map/{YYYYMMDDHHMMSS}.json は、指定した時刻のデータが存在しないと取得できません。
最新データを扱う場合は、先に latest_time.txt を取得して、その時刻を使うのが安全です。
観測項目は観測所によって異なる
すべての観測所がすべての項目を持っているわけではありません。
そのため、observation[key]?.[0] ?? null のように、値がない場合を考慮しておくと扱いやすくなります。
緯度・経度は変換が必要
amedastable.json の緯度・経度は [度, 分] の形式です。
Leafletなどの地図ライブラリで使う場合は、十進度へ変換します。
function toDecimalLatLon(value) {
const [degree, minute] = value;
return degree + minute / 60;
}
おわりに
気象庁のアメダスJSONを使うと、APIキーなしで全国の観測値を取得できます。
観測値JSONだけでは観測所番号しか分かりませんが、amedastable.json と組み合わせることで、観測所名や緯度・経度と結びつけられます。
緯度・経度が手に入ると、地図表示や可視化にもつなげやすくなります。
今回は現在時刻に近い1時点のデータを地図に表示しましたが、時刻を変えて取得すれば、気温や降水量の変化を時系列で見ることもできそうです。
参考リンク
参考:スクリプト全体
scripts/fetch_amedas.py
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from datetime import datetime
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
BASE_URL = "https://www.jma.go.jp/bosai/amedas"
LATEST_TIME_URL = f"{BASE_URL}/data/latest_time.txt"
AMEDAS_TABLE_URL = f"{BASE_URL}/const/amedastable.json"
MAP_URL = f"{BASE_URL}/data/map/{{timestamp}}.json"
def fetch_text(url: str) -> str:
with urlopen(url, timeout=20) as response:
return response.read().decode("utf-8")
def fetch_json(url: str) -> dict:
return json.loads(fetch_text(url))
def latest_timestamp() -> tuple[str, str]:
latest_time = fetch_text(LATEST_TIME_URL).strip()
dt = datetime.fromisoformat(latest_time)
return latest_time, dt.strftime("%Y%m%d%H%M%S")
def normalize_timestamp(value: str) -> str:
if value.isdigit() and len(value) == 14:
return value
dt = datetime.fromisoformat(value)
return dt.strftime("%Y%m%d%H%M%S")
def degree_minute_to_decimal(value: list[float]) -> float:
degree, minute = value
return degree + minute / 60
def observed_value(record: dict, key: str):
item = record.get(key)
if not item:
return None
return item[0]
def format_value(value, unit: str = "") -> str:
if value is None:
return "-"
return f"{value}{unit}"
def build_rows(amedas_map: dict, amedas_table: dict) -> list[dict]:
rows = []
for station_id, observation in amedas_map.items():
station = amedas_table.get(station_id, {})
lat = station.get("lat")
lon = station.get("lon")
rows.append(
{
"id": station_id,
"name": station.get("kjName", "(unknown)"),
"lat": degree_minute_to_decimal(lat) if lat else None,
"lon": degree_minute_to_decimal(lon) if lon else None,
"temp": observed_value(observation, "temp"),
"precipitation1h": observed_value(observation, "precipitation1h"),
"wind": observed_value(observation, "wind"),
"windDirection": observed_value(observation, "windDirection"),
}
)
return rows
def print_rows(rows: list[dict], limit: int) -> None:
print("観測所ID 観測所名 緯度 経度 気温 1時間降水量 風速 風向")
print("-" * 86)
for row in rows[:limit]:
lat = f"{row['lat']:.4f}" if row["lat"] is not None else "-"
lon = f"{row['lon']:.4f}" if row["lon"] is not None else "-"
print(
f"{row['id']:<9} "
f"{row['name']:<12} "
f"{lat:>8} "
f"{lon:>9} "
f"{format_value(row['temp'], '℃'):>8} "
f"{format_value(row['precipitation1h'], 'mm'):>11} "
f"{format_value(row['wind'], 'm/s'):>7} "
f"{format_value(row['windDirection']):>5}"
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="気象庁のアメダスJSONから観測値と観測所情報を取得して表示します。"
)
parser.add_argument(
"--time",
help=(
"取得する観測時刻。YYYYMMDDHHMMSS または ISO 8601 形式で指定します。"
"省略時は latest_time.txt の最新時刻を使います。"
),
)
parser.add_argument(
"--limit",
type=int,
default=20,
help="表示する観測所数。省略時は20件です。",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
if args.time:
latest_time_text = None
timestamp = normalize_timestamp(args.time)
else:
latest_time_text, timestamp = latest_timestamp()
amedas_map = fetch_json(MAP_URL.format(timestamp=timestamp))
amedas_table = fetch_json(AMEDAS_TABLE_URL)
except (HTTPError, URLError, TimeoutError) as error:
print(f"取得に失敗しました: {error}")
return 1
if latest_time_text:
print(f"最新観測時刻: {latest_time_text}")
print(f"取得URL: {MAP_URL.format(timestamp=timestamp)}")
print(f"観測所数: {len(amedas_map)}")
print()
rows = build_rows(amedas_map, amedas_table)
print_rows(rows, args.limit)
return 0
if __name__ == "__main__":
raise SystemExit(main())
examples/amedas_map.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>アメダス観測値マップ</title>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIINfQnl8k4B9cc1HkQVm3aG0gC7gYq+4fQ="
crossorigin=""
>
<style>
.leaflet-container {
overflow: hidden;
}
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer {
left: 0;
position: absolute;
top: 0;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
user-select: none;
}
.leaflet-map-pane {
z-index: 400;
}
.leaflet-tile-pane {
z-index: 200;
}
.leaflet-overlay-pane {
z-index: 400;
}
.leaflet-marker-pane {
z-index: 600;
}
.leaflet-popup-pane {
z-index: 700;
}
.leaflet-control {
pointer-events: auto;
position: relative;
z-index: 800;
}
.leaflet-top,
.leaflet-bottom {
pointer-events: none;
position: absolute;
z-index: 1000;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
html,
body {
height: 100%;
margin: 0;
}
body {
color: #1f2328;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#map {
height: 100%;
}
.toolbar {
align-items: center;
background: #fff;
border: 1px solid #d0d7de;
border-radius: 6px;
box-shadow: 0 2px 10px rgb(0 0 0 / 18%);
display: flex;
flex-wrap: wrap;
gap: 8px;
left: 12px;
padding: 10px;
position: fixed;
right: 12px;
top: 12px;
z-index: 1000;
}
.toolbar select,
.toolbar input,
.toolbar button {
border: 1px solid #d0d7de;
border-radius: 6px;
font: inherit;
height: 34px;
padding: 0 10px;
}
.toolbar input {
width: 180px;
}
.toolbar button {
background: #0969da;
color: #fff;
cursor: pointer;
}
.toolbar button:disabled {
background: #8c959f;
cursor: wait;
}
.status {
color: #57606a;
font-size: 13px;
margin-left: auto;
}
.popup-name {
font-weight: 700;
}
@media (max-width: 640px) {
.toolbar {
align-items: stretch;
flex-direction: column;
}
.toolbar input,
.toolbar select,
.toolbar button {
width: 100%;
}
.status {
margin-left: 0;
}
}
</style>
</head>
<body>
<div class="toolbar">
<select id="metric">
<option value="temp">気温</option>
<option value="precipitation1h">1時間降水量</option>
<option value="wind">風速</option>
</select>
<input id="time" placeholder="例: 20260627140000">
<button id="load">表示</button>
<div id="status" class="status">読み込み前</div>
</div>
<div id="map"></div>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
></script>
<script>
const BASE_URL = "https://www.jma.go.jp/bosai/amedas";
const LATEST_TIME_URL = `${BASE_URL}/data/latest_time.txt`;
const AMEDAS_TABLE_URL = `${BASE_URL}/const/amedastable.json`;
const MAP_URL = `${BASE_URL}/data/map`;
const METRICS = {
temp: { label: "気温", unit: "℃" },
precipitation1h: { label: "1時間降水量", unit: "mm" },
wind: { label: "風速", unit: "m/s" },
};
const metricSelect = document.querySelector("#metric");
const timeInput = document.querySelector("#time");
const loadButton = document.querySelector("#load");
const status = document.querySelector("#status");
const map = L.map("map", { zoomControl: false }).setView([36.5, 138], 5);
const markerLayer = L.layerGroup().addTo(map);
L.control.zoom({ position: "bottomright" }).addTo(map);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 18,
attribution: "© OpenStreetMap contributors",
}).addTo(map);
function toTimestamp(latestTimeText) {
return latestTimeText.replace(/\D/g, "").slice(0, 14);
}
function toDecimalLatLon(value) {
const [degree, minute] = value;
return degree + minute / 60;
}
function observedValue(observation, key) {
return observation[key]?.[0] ?? null;
}
function formatValue(value, unit) {
return value === null ? "-" : `${value}${unit}`;
}
function colorByMetric(metric, value) {
if (metric === "temp") {
if (value >= 30) return "#d73027";
if (value >= 25) return "#fc8d59";
if (value >= 20) return "#fee08b";
if (value >= 10) return "#91bfdb";
return "#4575b4";
}
if (metric === "precipitation1h") {
if (value >= 50) return "#7f0000";
if (value >= 30) return "#d7301f";
if (value >= 10) return "#fc8d59";
if (value > 0) return "#91bfdb";
return "#d9d9d9";
}
if (value >= 20) return "#54278f";
if (value >= 10) return "#756bb1";
if (value >= 5) return "#9e9ac8";
return "#cbc9e2";
}
async function fetchLatestTimestamp() {
const response = await fetch(LATEST_TIME_URL);
const latestTimeText = await response.text();
return toTimestamp(latestTimeText.trim());
}
async function fetchJson(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return response.json();
}
function buildRows(amedasMap, amedasTable) {
return Object.entries(amedasMap).flatMap(([stationId, observation]) => {
const station = amedasTable[stationId];
if (!station?.lat || !station?.lon) {
return [];
}
return {
id: stationId,
name: station.kjName,
lat: toDecimalLatLon(station.lat),
lon: toDecimalLatLon(station.lon),
temp: observedValue(observation, "temp"),
precipitation1h: observedValue(observation, "precipitation1h"),
wind: observedValue(observation, "wind"),
};
});
}
function renderMarkers(rows, metric) {
const metricInfo = METRICS[metric];
markerLayer.clearLayers();
rows
.filter((row) => row[metric] !== null)
.forEach((row) => {
const value = row[metric];
const marker = L.circleMarker([row.lat, row.lon], {
radius: 5,
color: "#24292f",
fillColor: colorByMetric(metric, value),
fillOpacity: 0.82,
weight: 0.6,
});
marker.bindPopup(`
<div class="popup-name">${row.name}</div>
観測所ID: ${row.id}<br>
${metricInfo.label}: ${formatValue(value, metricInfo.unit)}<br>
気温: ${formatValue(row.temp, "℃")}<br>
1時間降水量: ${formatValue(row.precipitation1h, "mm")}<br>
風速: ${formatValue(row.wind, "m/s")}
`);
marker.addTo(markerLayer);
});
}
async function loadAmedas() {
const metric = metricSelect.value;
loadButton.disabled = true;
status.textContent = "読み込み中";
try {
const timestamp = timeInput.value.trim() || await fetchLatestTimestamp();
const [amedasMap, amedasTable] = await Promise.all([
fetchJson(`${MAP_URL}/${timestamp}.json`),
fetchJson(AMEDAS_TABLE_URL),
]);
const rows = buildRows(amedasMap, amedasTable);
renderMarkers(rows, metric);
timeInput.value = timestamp;
status.textContent = `${timestamp} / ${rows.length}地点`;
} catch (error) {
console.error(error);
status.textContent = "取得に失敗しました";
} finally {
loadButton.disabled = false;
}
}
loadButton.addEventListener("click", loadAmedas);
metricSelect.addEventListener("change", loadAmedas);
loadAmedas();
</script>
</body>
</html>


