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?

気象庁サイトを利用したビューアの作成 15 警報+氾濫警報

0
Posted at

目次・全体的な注意点(第1回の記事)

今回は、一般の警報と氾濫警報(指定河川洪水予報)を同時に表示するページを作成していきます

警報・氾濫警報とは

気象に関する警報・注意報のうち、氾濫警報は気象庁と河川管理者(国土交通省や都道府県)が共同で、氾濫警報以外の警報は気象庁が単独で発表しています。氾濫警報以外の警報については気象観測によって発表できる一方、氾濫警報は雨量の他にダムの放流量など、河川管理者の水位調整によって水位が変わる場合もあることから、河川管理者と気象庁が共同して発表しています

このような事情から、氾濫警報とその他の警報では格納されているファイルや電文の構造が異なります

apiのURL

他の情報と同様、ブラウザの開発者ツールでネットワークタブを開いた状態で気象庁ホームページにアクセスし、Fetch/XHRでフィルタするとデータを格納したファイルが見つかります

一般の警報の発表状況は、

氾濫警報の発表状況は、

なお、一般の警報の各都道府県分のURL中の{officeCode}や、電文中に出現する地域ごとのコード番号は

apiの構造

気象庁ホームページの内部apiの構造は、気象庁防災情報XMLの構造をモデルとしているものが多いです。このため、気象庁防災情報XMLの解説資料が参考になる場合が多いです

一般の警報・氾濫警報の電文の説明は「気象警報・注意報(R06)_解説資料.pdf」「指定河川洪水予報(氾濫警報・注意報)_解説資料.pdf」としてそれぞれまとめられており、この資料の内容が参考になります

詳細な電文の仕様については、一般の警報については前々回の記事で氾濫警報に関しては前回の記事で説明しています

map.json
[
  {
    "reportDatetime": "2026-06-06T13:30:00+09:00",
    "infoType": "発表",
    "publishingOffice": "名瀬測候所",
    "warning": {
      "class20Items": [
        {
          "areaCode": "4622200",
          "kinds": [
            { "code": "29", "status": "継続"}
          ]
        },
        {
          "areaCode": "4622300",
          "kinds": [
            { "code": "29", "status": "解除"}
          ]
        },
        {
          "areaCode": "4630400",
          "kinds": [
            { "status": "発表警報・注意報はなし"}
          ]
        },......
      ]
    },
    "dataTypeCode": "VPWW56"
  },......
]

警報に関しては {i}.warning.class20Items.{j}.areaCode に2次細分区域(≒市町村)コードが、{i}.warning.class20Items.{j}.kinds.code に警報の種類が格納されます。{i}.warning.class20Items.{j}.kinds.status が「解除」でない場合は発表されていると判定できます。発表注意報がない場合には statusが「発表警報・注意報はなし」となり、code が出現しない場合もあるため注意してください

警報コードは警報の解説資料の別表に掲載がありますが、以下の通りです(かっこ付きの情報は2026年時点では運用されていません)

現象 特別警報 危険警報 警報 注意報
大雨 33 43 03 10
土砂災害 39 49 09 29
氾濫 34 44 04 18
高潮 38 48 08 19
大雪 36 (46) 06 12
暴風/強風 35 (45) 05 15
暴風雪/風雪 32 (42) 02 13
波浪 37 (47) 07 16
14
融雪 17
濃霧 20
乾燥 21
なだれ 22
低温 23
24
着氷 25
着雪 26
その他 (27)
flood_xml.json
[
  {
    "publishingOffice": "東京都 気象庁",
    "editorialOffice": "気象庁本庁",
    "reportDatetime": "2026-06-03T14:50:00+09:00",
    "infoType": "発表",
    "serial": "2",
    "item": {
      "areas": [
        { "name": "目黒川", "code": "130005000100"}
      ],
      "name": "レベル2氾濫注意報解除",
      "code": "10",
      "condition": "レベル2氾濫注意報解除"
    },
    "pdfFilename": "130005000100_20260602205000_n00.pdf",
    "riverCode": "130005000100",
    "riverName": "目黒川",
    "class20Codes": [ "1310900", "1311000"],
    "class10Codes": [ "130010"],
    "officeCodes": [ "130000"]
  },......
]

氾濫警報については、{i}.item.code に警報種別のコード番号が、{i}.class20Codes に対象の2次細分区域(≒市町村)コードが格納されています。一般の警報と同様に市町村単位で表現する場合には、1つの市町村が複数の川の流域に含まれる場合もあることから、一度全部の氾濫警報電文を読んで最も危険度が高いものをとる必要があります

警報のコード番号は氾濫警報の解説資料に掲載がありますが、以下の通りです。コード番号の上1桁が警戒レベルに対応しているので、上1桁が一番大きいものを市町村の氾濫の危険度と判定すればOKです

name code
レベル2氾濫注意報解除 10
レベル2氾濫注意報 20
レベル2氾濫注意報 21
レベル2氾濫注意報(警報解除) 22
レベル3氾濫警報 30
レベル3氾濫警報 31
レベル4氾濫危険警報 40
レベル4氾濫危険警報 41
レベル5氾濫特別警報 51
レベル5氾濫特別警報(氾濫水の予報) 53
area.json
{
  "offices": { // 都道府県
    "020000": {
      "name": "青森県",
      "parent": "010200",
      "children": [ "020010", "020020", "020030"]
    },
    "030000": {
      "name": "岩手県",
      "parent": "010200",
      "children": [ "030010", "030020", "030030"]
    },......
  },
  "class10s": { // 1次細分区域
    "020010": {
      "name": "津軽",
      "parent": "020000",
      "children": [ "020011", "020012", "020013", "020014"]
    },
    "020020": {
      "name": "下北",
      "parent": "020000",
      "children": [ "020020"]
    },......
  },
  "class15s": { // 市町村等をまとめた地域
    "020011": {
      "name": "東青津軽",
      "parent": "020010",
      "children": [ "0220100", "0230100", "0230300", "0230400", "0230700"]
    },
    "020012": {
      "name": "北五津軽",
      "parent": "020010",
      "children": [ "0220500", "0238100", "0238400", "0238700"]
    },......
  },
  "class20s": { // 2次細分区域(市町村等)
    "0220100": { "name": "青森市", "parent": "020011"},
    "0220200": { "name": "弘前市", "parent": "020014"},
    "0220300": { "name": "八戸市", "parent": "020031"},......
  }

area.json には各都道府県(offices)、1次細分区域(class10s)、市町村等をまとめた地域(class15s)、2次細分区域(≒市町村。class20s)の名称と包含関係が格納されています。今回は都道府県の選択欄の作成と、都道府県内の市町村の列挙に使用します

表示用のコード

ここまでの内容を使って、都道府県別の警報と氾濫警報を一覧表で表示するページを作成していきます

警報の危険度別の配色を定義します。配色は 気象庁ホームページの配色指針 を参考にしました

<style>
  *{ font-family:sans-serif;}
  table, tr, td, th{ border-collapse:collapse; white-space:nowrap;}
  th,td{ padding:2px 8px; border-style:solid; border-width:1px 0; border-color:#d8d8db;}
  th{ background-color:#f1f1f4;}
  .lv5{ background:#0c000c; color:white;}
  .lv4{ background:#aa00aa; color:white;}
  .lv3{ background:#ff2800; color:white;}
  .lv2{ background:#f2e700; color:black;}
</style>

警報コードごとの警報名とレベル(配色)、氾濫警報のレベルごとの警報名を定義します

const warnInfos = {
  "00":{"long":"解除","lv":0},
  "32":{"long":"暴風雪特別警報","lv":5},
  "33":{"long":"レベル5大雨特別警報","lv":5},
  // ......
  "29":{"long":"レベル2土砂災害注意報","lv":2}
}
const floodInfos = {
  "1":"解除",
  "2":"レベル2氾濫注意報",
  "3":"レベル3氾濫警報",
  "4":"レベル4氾濫危険警報",
  "5":"レべル5氾濫特別警報"
};

警戒対象の市町村名を表示するために必要な、地域情報を取得します

function getGlobals(){
  fetch("https://www.jma.go.jp/bosai/common/const/area.json")
  .then((response) => response.json())
  .then((response) => {
    areas = response;
    get();
  });
}

都道府県の選択欄を作成します。その際、単純に{area.json}['offices']を前から読んでいくと、100000(群馬県)が最初になってしまう場合があるため、011000(北海道宗谷地方)から順に並ぶよう、地域コードを数値比較してソーティングを行います

function makeOfficeSelect(){
  let officeSelect = "";
  let officeCodes = Object.keys(areas['offices']).sort((a,b)=>{return a-b});
  for( let officeCode of officeCodes){
    let officeName = areas['offices'][officeCode]['name'];
    officeSelect += "<option value='" + officeCode + "'>" + officeName + "</option>"
  }
  document.getElementById("officeSelect").innerHTML = officeSelect;
  get();
}

警報氾濫警報の全国一覧ファイルを取得します

function get(){
  let promises = [], datas = {};
  let urls = {
    "warns":"https://www.jma.go.jp/bosai/warning/data/r8/map.json",
    "floods":"https://www.jma.go.jp/bosai/flood/data/r8/flood_xml.json"
  }
  for( let elemName in urls){
    promises.push(
      new Promise((resolve, reject) => {
        fetch(urls[elemName])
        .then((response) =>  response.json())
        .then((response) => {
          datas[elemName] = response;
          resolve(elemName);
        });
      })
    );
  }
  Promise.allSettled(promises).then((values) => {
    display( datas['warns'], datas['floods']);
  });
}

一覧表を作成します。まず選択された都道府県コードを読み取り、{area.json}をoffices→class10s→class15sの順に下っていくことで包含されている市町村コード(clsss20Code)を列挙します

let out = "", officeCode = document.getElementById("officeSelect").value;
out += "<table>";
for( let class10Code of areas['offices'][officeCode]['children']){
  for( let class15Code of areas['class10s'][class10Code]['children']){
    for( let class20Code of areas['class15s'][class15Code]['children']){
      const class20Name = areas['class20s'][class20Code]['name'];
      out += "<tr><th>" + class20Name + "</th><td>";

警報電文を順番に読み込みます。areaCodeが対象の2次細分区域コード(class20Code)と一致し、statusが「解除」「発表警報・注意報はなし」でない場合に警報の種別と危険度を定数から読み取り、表示していきます

for( let report of warns){
  for( let class20Item of report['warning']['class20Items']){
    const areaCode = class20Item['areaCode'];
    if( areaCode != class20Code){
      continue;
    }
    for( let kind of class20Item['kinds']){
      const warnCode = kind['code'], warnStatus = kind['status'];
      if( warnStatus=="解除" || warnStatus=="発表警報・注意報はなし"){
        continue;
      }else{
        let warnLevel = warnInfos[warnCode]['lv'], warnName = warnInfos[warnCode]['long'];
        out += "<span class='lv" + warnLevel + "'>" + warnName + "</span> ";
      }
    }
  }
}

続いて、氾濫警報の電文を順番に読み込み、areaCodeが対象の2次細分区域コード(class20Code)と一致する場合に危険度の最大値をとっていきます。全電文を読み込み後、危険度が2以上の場合に氾濫警報の種別を表示していきます

let floodLevelMax = 1;
for( let report of floods){
  let floodLevel = report['item']['code'][0];
  for( let areaCode of report['class20Codes']){
    if( areaCode==class20Code){
      floodLevelMax = Math.max( floodLevel, floodLevelMax);
    }
  }
}
if( floodLevelMax>1){
  out += "<span class='lv" + floodLevelMax + "'>" + floodInfos[floodLevelMax] + "</span> ";
}
out += "</td></tr>";

表示ページ全体のソースコード

サンプルページ

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>警報</title>
  <style>
    *{ font-family:sans-serif;}
    table, tr, td, th{ border-collapse:collapse; white-space:nowrap;}
    th,td{ padding:2px 8px; border-style:solid; border-width:1px 0; border-color:#d8d8db;}
    th{ background-color:#f1f1f4;}
    .lv5{ background:#0c000c; color:white;}
    .lv4{ background:#aa00aa; color:white;}
    .lv3{ background:#ff2800; color:white;}
    .lv2{ background:#f2e700; color:black;}
  </style>
</head>
<body>
  <h1>警報(氾濫警報含む)</h1>
  <div id="menu">
    <select id="officeSelect"></select>
    <button id="get">更新</button>
  </div>
  <div id="out"></div>
  <script>
    "use strict";
    const warnInfos = {
      "00":{"long":"解除","lv":0},
      "32":{"long":"暴風雪特別警報","lv":5},
      "33":{"long":"レベル5大雨特別警報","lv":5},
      "35":{"long":"暴風特別警報","lv":5},
      "36":{"long":"大雪特別警報","lv":5},
      "37":{"long":"波浪特別警報","lv":5},
      "38":{"long":"レベル5高潮特別警報","lv":5},
      "39":{"long":"レベル5土砂災害特別警報","lv":5},
      "43":{"long":"レベル4大雨危険警報","lv":4},
      "48":{"long":"レベル4高潮危険警報","lv":4},
      "49":{"long":"レベル4土砂災害危険警報","lv":4},
      "02":{"long":"暴風雪警報","lv":3},
      "03":{"long":"レベル3大雨警報","lv":3},
      "05":{"long":"暴風警報","lv":3},
      "06":{"long":"大雪警報","lv":3},
      "07":{"long":"波浪警報","lv":3},
      "08":{"long":"レベル3高潮警報","lv":3},
      "09":{"long":"レベル3土砂災害警報","lv":3},
      "10":{"long":"レベル2大雨注意報","lv":2},
      "12":{"long":"大雪注意報","lv":2},
      "13":{"long":"風雪注意報","lv":2},
      "14":{"long":"雷注意報","lv":2},
      "15":{"long":"強風注意報","lv":2},
      "16":{"long":"波浪注意報","lv":2},
      "17":{"long":"融雪注意報","lv":2},
      "19":{"long":"レベル2高潮注意報","lv":2},
      "20":{"long":"濃霧注意報","lv":2},
      "21":{"long":"乾燥注意報","lv":2},
      "22":{"long":"なだれ注意報","lv":2},
      "23":{"long":"低温注意報","lv":2},
      "24":{"long":"霜注意報","lv":2},
      "25":{"long":"着氷注意報","lv":2},
      "26":{"long":"着雪注意報","lv":2},
      "27":{"long":"その他の注意報","lv":2},
      "29":{"long":"レベル2土砂災害注意報","lv":2}
    }
    const floodInfos = { "1":"解除", "2":"レベル2氾濫注意報", "3":"レベル3氾濫警報", "4":"レベル4氾濫危険警報", "5":"レべル5氾濫特別警報"};
    let areas;

    getGlobals();

    function getGlobals(){
      fetch("https://www.jma.go.jp/bosai/common/const/area.json")
      .then((response) => response.json())
      .then((response) => {
        areas = response;
        makeOfficeSelect();
      });
    }

    function makeOfficeSelect(){
      let officeSelect = "";
      let officeCodes = Object.keys(areas['offices']).sort((a,b)=>{return a-b});
      for( let officeCode of officeCodes){
        let officeName = areas['offices'][officeCode]['name'];
        officeSelect += "<option value='" + officeCode + "'>" + officeName + "</option>"
      }
      document.getElementById("officeSelect").innerHTML = officeSelect;
      get();
    }

    document.getElementById("officeSelect").addEventListener("change",function(e){
      get();
    });
    document.getElementById("get").addEventListener("click",function(e){
      get();
    });

    function get(){
      let promises = [], datas = {};
      let urls = {
        "warns":"https://www.jma.go.jp/bosai/warning/data/r8/map.json",
        "floods":"https://www.jma.go.jp/bosai/flood/data/r8/flood_xml.json"
      }
      for( let elemName in urls){
        promises.push(
          new Promise((resolve, reject) => {
            fetch(urls[elemName])
            .then((response) =>  response.json())
            .then((response) => {
              datas[elemName] = response;
              resolve(elemName);
            });
          })
        );
      }
      Promise.allSettled(promises).then((values) => {
        display( datas['warns'], datas['floods']);
      });
    }

    function display( warns, floods){
      let out = "", officeCode = document.getElementById("officeSelect").value;
      out += "<table>";
      for( let class10Code of areas['offices'][officeCode]['children']){
        for( let class15Code of areas['class10s'][class10Code]['children']){
          for( let class20Code of areas['class15s'][class15Code]['children']){
            const class20Name = areas['class20s'][class20Code]['name'];
            out += "<tr><th>" + class20Name + "</th><td>";
            for( let report of warns){
              for( let class20Item of report['warning']['class20Items']){
                const areaCode = class20Item['areaCode'];
                if( areaCode != class20Code){
                  continue;
                }
                for( let kind of class20Item['kinds']){
                  const warnCode = kind['code'], warnStatus = kind['status'];
                  if( warnStatus=="解除" || warnStatus=="発表警報・注意報はなし"){
                    continue;
                  }else{
                    let warnLevel = warnInfos[warnCode]['lv'], warnName = warnInfos[warnCode]['long'];
                    out += "<span class='lv" + warnLevel + "'>" + warnName + "</span> ";
                  }
                }
              }
            }
            let floodLevelMax = 1;
            for( let report of floods){
              let floodLevel = report['item']['code'][0];
              for( let areaCode of report['class20Codes']){
                if( areaCode==class20Code){
                  floodLevelMax = Math.max( floodLevel, floodLevelMax);
                }
              }
            }
            if( floodLevelMax>1){
              out += "<span class='lv" + floodLevelMax + "'>" + floodInfos[floodLevelMax] + "</span> ";
            }
            out += "</td></tr>";
          }
        }
      }
      out += "</table>";
      document.getElementById("out").innerHTML = out;
    }
  </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?