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?

気象庁のアメダスJSONを取得して地図に表示してみる

0
Last updated at Posted at 2026-06-27

はじめに

気象データを使ったアプリや可視化を作りたいとき、まず欲しくなるのが「いま各地で観測されている値」です。

気象庁のWebサイトでは、アメダスの観測値がJSON形式で公開されています。

この記事では、次の流れでアメダスの観測値を扱ってみます。

  1. PythonでアメダスJSONを取得する
  2. 観測所番号と観測所情報を結合する
  3. 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: "&copy; 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);
    });
}

これで、全国のアメダス観測所が地図上に表示されます。

気温を選ぶと、気温の高い地域が暖色、低い地域が寒色で表示されます。

表示例

気温で色分けした例です。

6425e259-14fb-4739-b76d-bee605a3991e.png

1時間降水量で色分けした例です。

2f611253-6f71-42bc-aeba-2ce5950ed3b3.png

風速で色分けした例です。

ad173df5-35ff-4e31-9a96-2068e24f5b96.png

サンプルスクリプト

今回作成したサンプルは次の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: "&copy; 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>
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?