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?

気象庁サイトを利用したビューアの作成 7 地震情報(2)

0
Posted at

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

4月20日に、三陸沖を震源とするマグニチュード7.7(暫定値)の大きな地震が発生しました。前回は地震情報のうち、比較的頻繁に発表される情報に対応したページを作成しましたが、この地震ではこのコードで表示できない情報も発表されています。今回は、発表頻度の少ない情報に対応したコードを追加していきます

発表頻度が低めの地震情報

南海トラフ地震関連解説情報

 南海トラフ周辺の地震活動について、定期的に行われる検討会の調査結果を主に発表するものです

{
  "Control": {
    "Title": "南海トラフ地震関連解説情報"
  },
  "Head": {
    "Title": "南海トラフ地震関連解説情報",
    "InfoKind": "南海トラフ地震に関連する情報",
    "Headline": {
      "Text": "第104回南海トラフ沿いの地震に関する評価検討会で、南海トラフ周辺の地殻活動を評価しました。"
    }
  },
  "Body": {
    "EarthquakeInfo": {
      "Text": "本日(4月7日)開催した第104回南海トラフ沿いの地震に関する評価検討会で評価した、南海トラフ周辺の地殻活動の調査結果は以下のとおりです……",
      "Appendix": "** (参考) 南海トラフ地震に関連する情報の種類 **……"
    }
  }
}

 Body.EarthquakeInfo.Text部に本文が、Body.EarthquakeInfo.Appendix部に解説が書かれているので、これらを読み取れるようにしておけば表示できます

南海トラフ地震臨時情報

 南海トラフ周辺で大きな地震が発生した際に、通常と比べて南海トラフ沿いの大地震が一定程度高まった場合に発表されます。また、その調査を開始/終了した場合にも発表されます

{
  "Control": {
    "Title": "南海トラフ地震臨時情報"
  },
  "Head": {
    "Headline": {
      "Text": "\n***これは訓練です***\n 本日(16日)13時35分頃に四国沖を震源とする地震が発生しました……"
    }
  },
  "Body": {
    "EarthquakeInfo": {
      "InfoKind": "南海トラフ地震臨時情報",
      "InfoSerial": {
        "Name": "巨大地震警戒"
      },
      "Text": "\n***これは訓練です***\n 本日(16日)13時35分頃に四国沖を震源とするマグニチュード8.2の地震が発生しました……",
      "Appendix": "\n** (参考) 南海トラフ地震に関連する情報の種類 **……"
    },
    "NextAdvisory": "\n 今後は、「南海トラフ地震関連解説情報」で地殻活動の状況等を発表します。次回の情報発表は、21時頃を予定しています。……"
  }
}

 解説情報の要素に加え、Body.EarthquakeInfo.Text.NextAdvisory部に次回発表予定が掲載されます

北海道・三陸沖後発地震注意情報

 北海道から東北地方北部にかけての沖合では、マグニチュード7程度の大地震が起きたしばらく後に東日本大震災級の巨大地震が発生する可能性が懸念されています。巨大地震が発生する可能性はM7級の大地震100回につき1回程度とされていますが、日々の備えを再確認してほしいという意味でM7級の地震発生後に注意を呼び掛けるために発表されています

{
  "Control": {
    "Title": "北海道・三陸沖後発地震注意情報"
  },
  "Head": {
    "Title": "北海道・三陸沖後発地震注意情報",
    "InfoKind": "北海道・三陸沖後発地震注意情報",
    "Headline": {
      "Text": "本日(20日)16時52分に三陸沖を震源とするモーメントマグニチュード(Mw)7.4の地震が発生しました……"
    }
  },
  "Body": {
    "EarthquakeInfo": {
      "InfoKind": "北海道・三陸沖後発地震注意情報",
      "Text": "本日(20日)16時52分に、三陸沖を震源とするマグニチュード(M)7.7の地震が発生しました……",
      "Appendix": "** (参考) 北海道・三陸沖後発地震注意情報について **……"
    }
  }
}

 南海トラフの解説情報と作りは同じです

顕著な地震の震源要素更新のお知らせ

 顕著な地震について、速報で伝えた震源からより詳しい精査を行って求めた精度の高い震源を知らせるものです

{
  "Control": {
    "Title": "顕著な地震の震源要素更新のお知らせ"
  },
  "Head": {
    "Title": "顕著な地震の震源要素更新のお知らせ",
    "InfoKind": "震源要素更新のお知らせ",
    "Headline": {
      "Text": "令和 8年 4月20日19時30分をもって、地震の発生場所と規模を更新します。"
    }
  },
  "Body": {
    "Earthquake": {
      "OriginTime": "2026-04-20T16:52:00+09:00",
      "ArrivalTime": "2026-04-20T16:53:00+09:00",
      "Hypocenter": {
        "Area": {
          "Name": "三陸沖",
          "Code": "288",
          "Coordinate": "+39.8+143.2-20000/",
          "Coordinate_WGS": "+3950.5+14309.4-19000/"
        }
      },
      "Magnitude": "7.7"
    },
    "Comments": {
      "FreeFormComment": "度単位の震源要素は、津波情報等を引き続き発表する場合に使用されます。"
    }
  }
}

 Body.Earthquake.Hypocenter.Area.Coordinate要素と、Body.Earthquake.Hypocenter.Area.Coordinate_WGS要素に精度の高い震源が格納されます。前者が度単位の震源要素、後者は度分単位の震源要素で例えば+3950.5+14309.4-19000/は「北緯39度50.5分、東経143度09.4分、深さ19000m(19km)」を意味します

震度情報の更新

 大きな地震の場合に多いですが、通信状態が悪くてすぐに震度が送られなかったり、震源から遠く離れていて後から震度を観測した地点の震度の情報が後から追加されて更新されることがあります

{
  "Body": {
    "Intensity": {
      "Observation": {
        "Pref": [
          {
            "Name": "大阪府",
            "Code": "27",
            "MaxInt": "1",
            "Revise": "追加",
            "Area": [
              {
                "Name": "大阪府北部",
                "Code": "520",
                "MaxInt": "1",
                "Revise": "追加",
                "City": [
                  {
                    "Name": "大阪此花区",
                    "Code": "2710400",
                    "MaxInt": "1",
                    "Revise": "追加",
                    "IntensityStation": [
                      {
                        "Name": "大阪此花区春日出北*",
                        "Code": "2710431",
                        "Int": "1",
                        "Revise": "追加",
                        "latlon": {
                          "lat": 34.68,
                          "lon": 135.45
                        }
                      }
                    ]
                  }
                ]
              }
            ]
          },

 そのような場合は、各観測点の震度にRevise要素が追加されるため、これを読み取った際に強調表示するなどの表示方法が可能です

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

サンプルページ
サンプルページ(Date APIを利用した版)

地震リスト

ほぼ元のままですが、タイトルが「顕著な地震の震源要素更新のお知らせ」の場合に気づきやすいよう、震度欄に「更」と表示するようにしています

<!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; text-align:center;}
    th,td{ padding:2px 8px; border-style:solid; border-width:1px 0; border-color:#d8d8db;}
    th{ background-color:#f1f1f4;}
    td.i7 { background:#b40068; color:white;}
    td.i6p{ background:#a50021; color:white;}
    td.i6m{ background:#ff2800; color:white;}
    td.i5p{ background:#ff9900; color:black;}
    td.i5m{ background:#ffe600; color:black;}
    td.i4 { background:#fae696; color:black;}
    td.i3 { background:#0041ff; color:white;}
    td.i2 { background:#00aaff; color:black;}
    td.i1 { background:#f2f2ff; color:black;}
  </style>
</head>
<body>
  <h1>地震情報(一覧)</h1>
  <div id="out"></div>
  <script>
    "use strict";

    get();

    function get(){
      fetch("https://www.jma.go.jp/bosai/quake/data/list.json")
      .then((response) => response.json())
      .then((reports) => display(reports));
    }

    function display( reports){
      let out = "";

      out += "<table><tr><th>発生時刻</th><th>震源</th><th>震度</th><th>詳細</th></tr>";
      for( let report of reports){
        out += "<tr>";
        if( report['at']==undefined){
          out += "<td></td>";
        }else{
          let arrivalTime = Temporal.ZonedDateTime.from( report['at']+"[Asia/Tokyo]");
          out += "<td>" + dateFormat(arrivalTime,'m/d h:n') + "</td>";
        }
        if( report['anm']==undefined || report['anm']==""){
          if( report['ttl']==undefined){
            out += "<td></td>";
          }else{
            out += "<td>(" + report['ttl'] + ")</td>"; // infoType
          }
        }else{
          out += "<td>" + report['anm'] + "</td>"; // areaName
        }
        if( report['ttl']=="顕著な地震の震源要素更新のお知らせ"){
          out += "<td><strong>更</strong></td>";
        }else if( report['maxi']==undefined){
          out += "<td></td>";
        }else{
          out += "<td class='i" + report['maxi'].replace("-","m").replace("+","p") + "'>" + report['maxi'] + "</td>"; // maxInt = maximumIntensity
        }
        if( report['json']==undefined){
          out += "<td></td>";
        }else{
          out += "<td><a href='./quake2.html?json=" + report['json'] + "'>詳細</a></td>";
        }
        out += "</tr>";
      }
      out += "</table>";
      document.getElementById("out").innerHTML = out;
    }

    function dateFormat( t, f='y-m-dTh:n:s', is24=false){ // for Temporal
      const j=["","","","","","","",""];const e=["","Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
      if(is24&&t.hour==0){t.substract({days:1});f=f.replace(/h/g,"24").replace(/H/g,24);}
      const y=t.year,m=t.month,d=t.day,h=t.hour,n=t.minute,s=t.second,w=t.dayOfWeek;
      f=f.replace(/y/g,("000"+y).slice(-4)).replace(/Y/g,y).replace(/m/g,("0"+m).slice(-2)).replace(/M/g,m).replace(/d/g,("0"+d).slice(-2)).replace(/D/g,d);
      f=f.replace(/w/g,j[w]).replace(/W/g,e[w]);
      f=f.replace(/h/g,("0"+h).slice(-2)).replace(/H/g,h).replace(/n/g,("0"+n).slice(-2)).replace(/N/g,n).replace(/s/g,("0"+s).slice(-2)).replace(/S/g,s);
      return f;
    }
  </script>
</body>
</html>

個別の地震情報

<!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;}
    th,td{ padding:2px 8px; border-style:solid; border-width:1px 0; border-color:#d8d8db;}
    th{ background-color:#f1f1f4; white-space:nowrap;}
    span.i7 { background:#b40068; color:white;}
    span.i6p{ background:#a50021; color:white;}
    span.i6m{ background:#ff2800; color:white;}
    span.i5p{ background:#ff9900; color:black;}
    span.i5m{ background:#ffe600; color:black;}
    span.i4 { background:#fae696; color:black;}
    span.i3 { background:#0041ff; color:white;}
    span.i2 { background:#00aaff; color:black;}
    span.i1 { background:#f2f2ff; color:black;}
    em{ font-style:normal; background:#ffff80;}
    p{ margin-top:16px; margin-bottom:0;}
  </style>
</head>
<body>
  <h1>地震情報</h1>
  <div id="out"></div>
  <script>
    "use strict";
    const params = new URLSearchParams(window.location.search);


    get();

    function get(){
      fetch("https://www.jma.go.jp/bosai/quake/data/" + params.get("json"))
      .then((response) => response.json())
      .then((report) => display(report));
    }

    function display( report){
      let out = "";

      let reportDatetime = Temporal.ZonedDateTime.from( report['Head']['ReportDateTime']+"[Asia/Tokyo]");
      out += "<table>";
      out += "<tr><th>情報名</th><td>" + report['Head']['Title'] + "</td></tr>";
      out += "<tr><th>発表時刻</th><td>" + dateFormat(reportDatetime,'y/m/d h:n') + " " + report['Head']['InfoType'] + "</td></tr>";
      if( report['Body']['Earthquake']!=undefined){
        if( report['Body']['Earthquake']['OriginTime']!=undefined){
          let originTime = Temporal.ZonedDateTime.from( report['Body']['Earthquake']['OriginTime']+"[Asia/Tokyo]");
          out += "<tr><th>発生時刻</th><td>" + dateFormat(originTime,'d日h時n分') + "ごろ</td></tr>";
        }
        if( report['Body']['Earthquake']['Hypocenter']!=undefined){
          if( report['Body']['Earthquake']['Hypocenter']['Area']!=undefined){
            out += "<tr><th>震源</th><td>" + report['Body']['Earthquake']['Hypocenter']['Area']['Name'] + "</td></tr>";
            let coordinate = report['Body']['Earthquake']['Hypocenter']['Area']['Coordinate'];
            if( report['Body']['Earthquake']['Hypocenter']['Area']['Coordinate_WGS']!=undefined){ // Coordinate_WGSが定義されている場合はそちらに読み替え
              coordinate = report['Body']['Earthquake']['Hypocenter']['Area']['Coordinate_WGS'];
            }
            if( coordinate!=undefined){
              coordinate = coordinate.split(/(?=[-+])/); 
              if( coordinate.length>=3){
                let depth = coordinate[2].replace("/","") / 1000 * (-1);
                if( depth==0){
                  out += "<tr><th>深さ</th><td>" + "ごく浅い</td></tr>";
                }else{
                  out += "<tr><th>深さ</th><td>" + depth + "キロ</td></tr>";
                }
              }
            }
          }
        }
        if( report['Body']['Earthquake']['Magnitude']!=undefined){
          out += "<tr><th>マグニチュード</th><td>" + report['Body']['Earthquake']['Magnitude'] + "</td></tr>";
        }
      }
      if( report['Body']['Comments']!=undefined){
        out += "<tr><th></th><td>";
        for( let commentType in report['Body']['Comments']){
          if( report['Body']['Comments'][commentType]['Text']!=undefined){
            out += report['Body']['Comments'][commentType]['Text'].replaceAll("\n","<br>") + "<br>";
          }
        }
        out += "</td></tr>";
        if( report['Body']['Comments']['FreeFormComment']!=undefined){
          out += "<tr><th></th><td>" + report['Body']['Comments']['FreeFormComment'].replaceAll("\n","<br>") + "</td></tr>";
        }
      }
      if( report['Body']['EarthquakeInfo']!=undefined){
        for( let commentType of ['Text','Appendix']){
          if( report['Body']['EarthquakeInfo'][commentType]!=undefined){
            out += "<tr><th></th><td>";
            out += report['Body']['EarthquakeInfo'][commentType].replaceAll("\n","<br>") + "<br>";
            out += "</td></tr>";
          }
        }
      }
      if( report['Body']['Intensity']!=undefined && report['Body']['Intensity']['Observation']!=undefined && report['Body']['Intensity']['Observation']['Pref']!=undefined){
        let ints = {"area":{},"city":{},"obs":{}};
        for( let pref of report['Body']['Intensity']['Observation']['Pref']){
          let prefName = pref['Name'];
          for( let area of pref['Area']){
            let areaName = area['Name'], areaInt = area['MaxInt'];
            if( area['Revise']!=undefined){
              areaName += "<em>[" + area['Revise'] + "]</em>";
            }
            if( ints['area'][areaInt]==undefined){
              ints['area'][areaInt] = [];
            }
            ints['area'][areaInt].push( areaName);
            if( area['City']==undefined){
              continue;
            }
            for( let city of area['City']){
              let cityName = city['Name'], cityInt = city['MaxInt'];
              if( city['Revise']!=undefined){
                cityName += "<em>[" + city['Revise'] + "]</em>";
              }
              if( ints['city'][cityInt]==undefined){
                ints['city'][cityInt] = [];
              }
              if( ints['city'][cityInt][prefName]==undefined){
                ints['city'][cityInt][prefName] = [];
              }
              ints['city'][cityInt][prefName].push( cityName);
              for( let obs of city['IntensityStation']){
                let obsName = obs['Name'], obsInt = obs['Int'];
                if( obs['Revise']!=undefined){
                  obsName += "<em>[" + obs['Revise'] + "]</em>";
                }
                if( ints['obs'][obsInt]==undefined){
                  ints['obs'][obsInt] = {};
                }
                if( ints['obs'][obsInt][prefName]==undefined){
                  ints['obs'][obsInt][prefName] = [];
                }
                ints['obs'][obsInt][prefName].push( obsName);
              }
            }
          }
        }
        const intsArray = ["7", "6+", "6-", "5+", "震度5弱以上未入電", "5-", "4", "3", "2", "1"];
        const areaInfos = {"area":"地域","city":"市町村","obs":"観測点"};
        for( let areaType of ['area','city','obs']){
          if( Object.keys(ints[areaType]).length==0){
            continue;
          }
          out += "<tr><th>" + areaInfos[areaType] + "ごとの震度</th><td>";
          for( let int of intsArray){
            if( ints[areaType][int]==undefined){
              continue;
            }
            out += "<p>";
            out += "<span class='i" + int.replace("震度5弱以上未入電","5m").replace("+","p").replace("-","m") + "'>";
            out += "震度" + int.replace("-","").replace("+","").replace("震度5弱以上未入電","5弱以上と推定されるが未受信/精査中");
            out += "</span> ";
            if( areaType=="area"){
              for( let areaName of ints[areaType][int]){
                out += areaName + " ";
              }
              out += "<br>";
            }else{
              out += "<table>";
              for( let prefName in ints[areaType][int]){
                out += "<tr><th>" + prefName + "</th><td>";
                for( let areaName of ints[areaType][int][prefName]){
                  out += areaName + " ";
                }
                out += "</td></tr>";
              }
              out += "</table>";
            }
            out += "</p>";
          }
          out += "</td></tr>";
        }
      }
      out += "</table>";
      document.getElementById("out").innerHTML = out;
    }

    function dateFormat( t, f='y-m-dTh:n:s', is24=false){ // for Temporal
      const j=["","","","","","","",""];const e=["","Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
      if(is24&&t.hour==0){t.substract({days:1});f=f.replace(/h/g,"24").replace(/H/g,24);}
      const y=t.year,m=t.month,d=t.day,h=t.hour,n=t.minute,s=t.second,w=t.dayOfWeek;
      f=f.replace(/y/g,("000"+y).slice(-4)).replace(/Y/g,y).replace(/m/g,("0"+m).slice(-2)).replace(/M/g,m).replace(/d/g,("0"+d).slice(-2)).replace(/D/g,d);
      f=f.replace(/w/g,j[w]).replace(/W/g,e[w]);
      f=f.replace(/h/g,("0"+h).slice(-2)).replace(/H/g,h).replace(/n/g,("0"+n).slice(-2)).replace(/N/g,n).replace(/s/g,("0"+s).slice(-2)).replace(/S/g,s);
      return f;
    }
  </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?