震度分布図を作ろう⑨ ～震度速報を描画しよう～

## はじめ
みなさんこんにちは。

### 前回したこと
前回は、ちょっとした改修と塗りつぶしを行いましたね。

### 今回すること
今回は震度速報の描画をやっていきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/18c44b09-1a03-9588-66de-c49cde3350dc.png)
↑これ
震度3？以上の地震が起こってから震源や詳しい観測点ごとの震度が確定していないときに、大まかな地域ごとに出るものです。

## 開発スタート
### 前回までのコード
前回までのコードは

https://qiita.com/kottaro123456/items/6d5719347801154f03db#%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%AB

に貼ってありますので、コピーして利用ください。

### その前に…

:::note info
震度の観測点や細分区域の中心座標をまとめたJSONを作り直しました。
https://nanka.cloudfree.jp/webapps/shindobunpu/source/old/JMAPoints.js や
https://nanka.cloudfree.jp/webapps/shindobunpu/source/old/JMAstations.json 
を更新しているので<font color="red">**再ダウンロードしておいてください。**</font>
:::

<font color="red">**↑これまじで重要です。まじでダウンロードしておいてください。後々エラーのもととなります。**</font>


### ちょっとした改修
今回もちょっとした改修から。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/a35be55c-ef25-92ed-5f15-7de367067b18.png)
まずはこれを見てほしいんですけど、これは
<font color="#3333ff">`JMAstations.json`</font>：震度観測点の名前、座標等が入ったファイル
<font color="#dd0000">`saibun.geojson`</font>：日本地図が入ったファイル
で、日本地図のほうが4倍くらい重いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/05f17a97-5268-3431-96fc-07be679a79f1.png)

そして、今のこれらを読み込むコードはこんな感じです。
この読み込みの動作をわかりやすくすると、
![プレゼンテーション1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/209d94e6-6b9a-e805-59d1-547067df28e5.png)
こんな感じとなります。（ブラウザが関西弁で動いているかは知りませんけど）

最近のネット回線は十分速いので、どちらも一瞬で読み込んでエラーになることはあまりありません。
しかし、独自のサーバーやモバイル回線では読み込む速度が遅く、地図が読み込まれていないでエラーとなることがあります。

ではこのエラーをどう解決するかというと、
![KGFXFfR3s0GxOse1734835154_1734835194.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/994713bb-5bcb-ae62-95fe-15abc82d94ce.png)
早くできた人は遅い人を待ってあげればいいわけです。
すべてが読み終わるまで、いったん作業はストップさせて、全て読み終わってから震度分布図を描画することで、今までより確実に描画できることと、今までより早く読み込みができるようになりました。

この仕組みは
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/28217ce7-f4b4-adc7-53c0-03b78a357fab.png">
この`Promise.all([])`によって実現できます。
`Promise.all([Promiseの配列])`というのは、`Promiseの配列`を処理をすべて終了したときのみ、次の処理（この場合だと`await QuakeSelect(0);`）を実行するというものです。
これらの中の関数の改名や中身は省略しますので、知りたい人は各自で下の出来上がったコードから見てくださいませ🙏

また、情報更新の部分もちょっとした改修を行っておきました。
情報更新の際、今までであれば一番最初のものに戻ってしまっていたものが、選択中のまま更新できるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/443e0b09-6b8e-f964-ea59-9537eb272281.png)


### ①下準備
下準備と言っても特にこれというものはありません。
塗りつぶしする地図も細分区域でいいですからね～

### ②if文の改修
まずは各地の震度と震度速報を分けるif文を改修していきます。
```javascript:index.js:169
if (QuakeJson[num]["issue"]["type"] != "ScalePrompt") { //各地の震度に関する情報
    …
}
```
と、`"ScalePrompt"`つまり、震度速報を否定しているif文が怪しいですね。
```javascript:index.js:169
if (QuakeJson[num]["issue"]["type"] != "ScalePrompt") { //各地の震度に関する情報
    …
} else { // 震度速報
    // 震度速報のコードをここに書いていきます。
}
```
ではこの間に、「タイプが 震度速報でない ではない」つまり、「震度速報である」とき（？？？）
の処理を書いていきましょう！

### ③処理を書く
```diff_javascript:index.js:169
if (QuakeJson[num]["issue"]["type"] != "ScalePrompt") { //各地の震度に関する情報
    …
} else { // 震度速報
+    var icon_theme = "jqk";
    var latlon;
    var latList = [];
    var lonList = [];
    QuakeJson[num]["points"].forEach(element => {
        var ImgUrl = "";
        var PointShindo = "";
        var PointColor;
        if (element["scale"] == 10) {
            eval('PointColor = '+icon_theme+'_backColor_1');
            ImgUrl = "source/"+icon_theme+"_int1.png";
            PointShindo = "震度1";
        } else if (element["scale"] == 20) {
            eval('PointColor = '+icon_theme+'_backColor_2');
            ImgUrl = "source/"+icon_theme+"_int2.png";
            PointShindo = "震度2";
        } else if (element["scale"] == 30) {
            eval('PointColor = '+icon_theme+'_backColor_3');
            ImgUrl = "source/"+icon_theme+"_int3.png";
            PointShindo = "震度3";
        } else if (element["scale"] == 40) {
            eval('PointColor = '+icon_theme+'_backColor_4');
            ImgUrl = "source/"+icon_theme+"_int4.png";
            PointShindo = "震度4";
        } else if (element["scale"] == 45) {
            eval('PointColor = '+icon_theme+'_backColor_50');
            ImgUrl = "source/"+icon_theme+"_int50.png";
            PointShindo = "震度5弱";
        } else if (element["scale"] == 46) {
            eval('PointColor = '+icon_theme+'_backColor_50');
            ImgUrl = "source/"+icon_theme+"_int_.png";
            PointShindo = "震度5弱以上と推定";
        } else if (element["scale"] == 50) {
            eval('PointColor = '+icon_theme+'_backColor_55');
            ImgUrl = "source/"+icon_theme+"_int55.png";
            PointShindo = "震度5強";
        } else if (element["scale"] == 55) {
            eval('PointColor = '+icon_theme+'_backColor_60');
            ImgUrl = "source/"+icon_theme+"_int60.png";
            PointShindo = "震度6弱";
        } else if (element["scale"] == 60) {
            eval('PointColor = '+icon_theme+'_backColor_65');
            ImgUrl = "source/"+icon_theme+"_int65.png";
            PointShindo = "震度6強";
        } else if (element["scale"] == 70) {
            eval('PointColor = '+icon_theme+'_backColor_7');
            ImgUrl = "source/"+icon_theme+"_int7.png";
            PointShindo = "震度7";
        } else {
            eval('PointColor = '+icon_theme+'_backColor__');
            ImgUrl = "source/"+icon_theme+"_int_.png";
            PointShindo = "震度不明";
        }
        var area_Code = AreaNameToCode(element["addr"]);
        latlon = FillPolygon(area_Code, PointColor);
        latList.push(Number(latlon["lat"]));
        lonList.push(Number(latlon["lng"]));
        let shindoIcon = L.icon({
            iconUrl: ImgUrl,
            iconSize: [30, 30],
            popupAnchor: [0, -50]
        });
        var shindo_icon = L.marker(latlon, { icon: shindoIcon,pane: eval('\"shindo'+element["scale"]+'\"') });
        shindo_icon.bindPopup('<ruby>'+element["addr"] + '<rt style="font-size: 0.7em;">' + AreaNameToKana(element["addr"]) + '</rt></ruby>　'+ PointShindo,{closeButton: false, zIndexOffset: 10000,autoPan: false,});
        shindo_icon.on('mouseover', function (e) {
            this.openPopup();
        });
        shindo_icon.on('mouseout', function (e) {
            this.closePopup();
        });
        shindo_layer.addLayer(shindo_icon);
        // console.log(element["addr"] + " " + PointShindo + " OK");
    });
    const aryMax = function (a, b) {return Math.max(a, b);}
    const aryMin = function (a, b) {return Math.min(a, b);}
    var latMax = latList.reduce(aryMax);
    var latMin = latList.reduce(aryMin);
    var lonMax = lonList.reduce(aryMax);
    var lonMin = lonList.reduce(aryMin);
    //通常時の位置初期化の位置
    shingenLatLng = new L.LatLng(Number((latMax+latMin)/2), Number((lonMax+lonMin)/2));
    latList = [];
+    lonList = [];
}
```
震度によってアイコンや色を変えるところは、各地の震度の処理と同じです。

### ④謎の座標処理
ここまでは一緒ですが、次の

```javascript:コード①
 1 latlon = FillPolygon(area_Code, PointColor);
 2 latList.push(Number(latlon["lat"]));
 3 lonList.push(Number(latlon["lng"]));
```

```javascript:コード①つづき
 4 const aryMax = function (a, b) {return Math.max(a, b);}
 5 const aryMin = function (a, b) {return Math.min(a, b);}
 6 var latMax = latList.reduce(aryMax);
 7 var latMin = latList.reduce(aryMin);
 8 var lonMax = lonList.reduce(aryMax);
 9 var lonMin = lonList.reduce(aryMin);
10 shingenLatLng = new L.LatLng(Number((latMax+latMin)/2), Number((lonMax+lonMin)/2));
11 latList = [];
12 lonList = [];
```

とこらへんから何をしているのかわからなくなってくると思います。
簡単にここでしていることを書いてみました。

そもそも、震度速報というのは、震度３？以上の地震が起こってからすぐに発表されるものであり、**震源はまだ決まっていません。**
よって、描画したあとに地震が起こった地域に地図を移動させたいのですが、
震源がわからないからと地図を移動させなければどこで地震が起こったのか分かりません（左）し、
どこかの震源アイコンの座標に移動してしまえば、区域数が少なければいいですが、広範囲の地震であれば中心とは離れてしまいます（右）。
![1734869119.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/210365a0-5ffa-74bd-0c95-eb6fb35aa1a2.png)

そこで、解決策としてアイコンの緯度経度から最大・最小をそれぞれ求め、それの中心を求めることで表示する地図の中心を求めたいわけです。
![プレゼンテーション1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/44d4070d-76a1-81ec-aaa4-7ed81b18ba7c.png)

ではさっそくコードの解説にうつっていきます。

以下、先ほどの抜粋のコード①での行数を下に解説していきます。

#### １行目
まず、抜粋１行目の
```javascript
latlon = FillPolygon(area_Code, PointColor);
```
latlonという変数に関数を代入しています。何をしているのでしょうか？
実は、`変数 = function();`という形にすると、`function();`の返り値を`変数`に代入することができます。
![プレゼンテーション1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/182eaf32-f959-3ad4-012b-c1cd01a75eed.png)
↑こんな感じ（わかりにくい）

そして、`FillPolygon()`というのは何を返してくれるかということですが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/3aa1e62e-5d5e-ac2b-edd2-989e240df9df.png)
`centerPoint[area_code]`というのは、各細分区域の中心座標（アイコンを配置するところ）を`{"lat": 24.455312, "lng": 122.977493}`のように呼び出します。
そして、それを<font color="#F92672">`return`</font>しているので、上の簡略図の黄色矢印のように、`(1行目の)latlon`の中身は指定した`area_code`の中心座標です。

#### ２～３行目
```javascript
latList.push(Number(latlon["lat"]));
lonList.push(Number(latlon["lng"]));
```
は何をしているかというと、`latList`とか`lonList`という中心座標を集めるための配列に、1行目で得られた座標を追加しています。
`配列.push(要素)`というのは、配列の1番最後に要素を追加するコードです。

#### ４～５行目
```javascript
const aryMax = function (a, b) {return Math.max(a, b);}
const aryMin = function (a, b) {return Math.min(a, b);}
```
これは、次の`reduce`メソッドから与えられる２つの値`a`と`b`を比べて大きい方を返すものです。（`a`と`b`の最大・最小を求めていますが、結局２つなので大きい方・小さい方の意味になります。）

#### ６～９行目
```javascript
var latMax = latList.reduce(aryMax);
var latMin = latList.reduce(aryMin);
var lonMax = lonList.reduce(aryMax);
var lonMin = lonList.reduce(aryMin);
```
ここで、ようやく緯度経度の最小最大を求めています。
`reduce`メソッドは便利なもので、順番の値とそれまでの累積の結果を比べてくれるものです。
![プレゼンテーション1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/0a3d41e5-0ae6-66f4-c309-2eb5d9d7b3f2.png)
こうやって最後に残ったものが結果となり、`latMax`等に入ります。

#### 10行目
```javascript
shingenLatLng = new L.LatLng(Number((latMax+latMin)/2), Number((lonMax+lonMin)/2));
```
ここで、これまでの結果をまとめて座標を出しています。
（最大値＋最小値）÷２することで中央を出しています。

#### 11～12行目
```javascript
latList = [];
lonList = [];
```
そして、次にもう一回したときに前の結果が残っていないように、今計算したものはおさらばします。

### ⑤震度速報描画完了！
さてこれで震度速報が描画できました！
さっそく描画してみましょう！
![1734871607.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/ba96a7cc-38d5-48de-ef2c-5069d53384dc.png)
震度速報が描画できていますね！
そして、良い位置に地図が移動できていると思います。

## 終わりに
今回は震度速報の描画をやっていきました。
これまでの震源アイコンや塗りつぶしを組み合わせていくだけで、新しい処理も座標を求めるものくらいではなかったのではないでしょうか。
結構震度分布図としてはある程度はできてきたので次は何をしようか迷っている感じです。
んー。またちょっとした改修かな。
とりあえず、今回も最後まで読んでいただき本当にありがとうございました！！

今回のサンプル
[https://nanka.cloudfree.jp/bin/webapps/shindobunpu_qiita/9/](https://nanka.cloudfree.jp/bin/webapps/shindobunpu_qiita/9/)

<details><summary>今回のソースコード</summary>

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>震度分布図</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.1/dist/js.cookie.min.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://nanka.cloudfree.jp/static/font/fonts.css">
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div id="map"></div>
    <div class="btns">
        <select id="quakelist"><option>地震情報の取得中…</option></select>
        <span class="setsumei" style="padding: 0;padding-right: 5px;"><button id="reload">情報更新</button> <input type="number" id="reload_num" value="20" max="100">件</span>
    </div>
    <script src="source/SmoothWheelZoom.js"></script>
    <script src="source/JMAPoints.js"></script>
    <script src="index.js"></script>
</body>
</html>
```

```javascript:index.js
var QuakeJson;
var JMAPointsJson;

var map = L.map('map', {
    preferCanvas: false,
    scrollWheelZoom: false,
    smoothWheelZoom: true,
    smoothSensitivity: 1.5,
}).setView([36.575, 137.984], 6);
L.control.scale({ maxWidth: 150, position: 'bottomright', imperial: false }).addTo(map);
map.zoomControl.setPosition('topright');
map.attributionControl.addAttribution(
    "<a href='https://www.jma.go.jp/jma/index.html' target='_blank'>気象庁</a>"
);
map.attributionControl.addAttribution(
    "<a href='https://github.com/mutsuyuki/Leaflet.SmoothWheelZoom' target='_blank'>SmoothWheelZoom</a>"
);

//地図に表示させる上下の順番
map.createPane("pane_map1").style.zIndex = 1; //地図（背景）
map.createPane("pane_map2").style.zIndex = 2; //地図（市町村）
map.createPane("pane_map3").style.zIndex = 3; //地図（細分）
map.createPane("pane_map_filled").style.zIndex = 5; //塗りつぶし
map.createPane("shindo10").style.zIndex = 10;
map.createPane("shindo20").style.zIndex = 20;
map.createPane("shindo30").style.zIndex = 30;
map.createPane("shindo40").style.zIndex = 40;
map.createPane("shindo45").style.zIndex = 45;
map.createPane("shindo46").style.zIndex = 46;
map.createPane("shindo50").style.zIndex = 50;
map.createPane("shindo55").style.zIndex = 55;
map.createPane("shindo60").style.zIndex = 60;
map.createPane("shindo70").style.zIndex = 70;
map.createPane("shingen").style.zIndex = 100; //震源
map.createPane("tsunami_map").style.zIndex = 110; //津波

var PolygonLayer_Style_nerv = {
    "color": "#ffffff",
    "weight": 1.5,
    "opacity": 1,
    "fillColor": "#3a3a3a",
    "fillOpacity": 1
}

//地震情報リストをクリックしたときの発火イベント
var list = document.getElementById('quakelist');
list.onchange = event => {
    Cookies.set("listSelectedIndex", list.selectedIndex);
    QuakeSelect(list.selectedIndex);
}
//ボタン押下時のイベント設定とローカルストレージの設定
document.getElementById('reload').addEventListener("click",()=>{
    if (document.getElementById('reload_num').value != "") {
        if (document.getElementById('reload_num').value > 100 || document.getElementById('reload_num').value <= 0) {
            reloadData(100)
        } else {
            reloadData(document.getElementById('reload_num').value);
        }
    } else {
        reloadData();
    }
    document.getElementById('reload').innerText = "更新中…";
});

var koushin_ok;
async function reloadData(reloadOption) {
    clearTimeout(koushin_ok);
    await GetQuake(reloadOption);
    await QuakeSelect(Cookies.get("listSelectedIndex"));
    document.getElementById('reload').innerText = "更新完了";
    koushin_ok = setTimeout(() => {
        document.getElementById('reload').innerText = "情報更新";
    }, 1000);
};

(async () => {
    await Promise.all([
        GetJson(),
        GetSaibun(),
        GetQuake()
    ]);
    Cookies.set("listSelectedIndex", 0);
    QuakeSelect(0);
})();

var japan_data;
async function GetSaibun() {
    const response = await fetch("../saibun.geojson");
    const data = await response.json();
    japan_data = data;
    L.geoJson(data, {
        pane: "pane_map3",
        style: PolygonLayer_Style_nerv
    }).addTo(map);
}
async function GetJson() {
    const response = await fetch("source/JMAstations.json");
    JMAPointsJson = await response.json();
}

async function GetQuake(option) {
    var url;
    if (!isNaN(option)) {
        url = "https://api.p2pquake.net/v2/history?codes=551&limit="+option;
    } else {
        url = "https://api.p2pquake.net/v2/history?codes=551&limit=20";
    }
    
    const response = await fetch(url);
    QuakeJson = await response.json();

    while (document.getElementById('quakelist').lastChild) {
        document.getElementById('quakelist').removeChild(document.getElementById('quakelist').lastChild);
    }

    var forEachNum = 0;
    QuakeJson.forEach(element => {
        var option = document.createElement("option");
        var text;
        let maxInt_data = element['earthquake']['maxScale'];
        let maxIntText = hantei_maxIntText(maxInt_data);
        let Name = hantei_Name(element['earthquake']['hypocenter']['name']);
        let Time = element['earthquake']['time'];
        if (element["issue"]["type"] == "ScalePrompt") {
            text = "【震度速報】" + element["points"][0]["addr"] + "など " + "\n" + Time.slice(0, -3) + "\n最大震度 : " + maxIntText;
        } else if (element["issue"]["type"] == "Foreign") {
            text = "【遠地地震】" + Time.slice(0, -3) + " " + Name;
        } else {
            text = Time.slice(0, -3) + " " + Name + " " +  "\n" + "\n最大震度 : " + maxIntText;
        }
        option.value = "" + forEachNum + "";
        option.textContent = text;
        document.getElementById('quakelist').appendChild(option);
        forEachNum++;
    });
}

var shingenIcon;
var shindo_icon;
var shindo_layer = L.layerGroup();
var shindo_filled_layer = L.layerGroup();
var filled_list = {};
var icon_theme = "jqk";

async function QuakeSelect(num) {
    list.options[Cookies.get("listSelectedIndex")].selected = true;

    if (shingenIcon && shindo_layer && shindo_filled_layer) {
        map.removeLayer(shingenIcon);
        map.removeLayer(shindo_layer);
        map.removeLayer(shindo_filled_layer);
        shingenIcon = "";
        shindo_layer = L.layerGroup();
        shindo_filled_layer = L.layerGroup();
        filled_list = {};
        shindo_icon = "";
    }
    let maxInt_data = QuakeJson[num]['earthquake']['maxScale'];
    var maxIntText = hantei_maxIntText(maxInt_data);
    var Magnitude = hantei_Magnitude(QuakeJson[num]['earthquake']['hypocenter']['magnitude']);
    var Name = hantei_Name(QuakeJson[num]['earthquake']['hypocenter']['name']);
    var Depth = hantei_Depth(QuakeJson[num]['earthquake']['hypocenter']['depth']);
    var tsunamiText = hantei_tsunamiText(QuakeJson[num]['earthquake']['domesticTsunami']);
    var Time = QuakeJson[num]['earthquake']['time'];

    var shingenLatLng = new L.LatLng(QuakeJson[num]["earthquake"]["hypocenter"]["latitude"], QuakeJson[num]["earthquake"]["hypocenter"]["longitude"]);
    var shingenIconImage = L.icon({
        iconUrl: 'source/shingen.png',
        iconSize: [40, 40],
        iconAnchor: [20, 20],
        popupAnchor: [0, -40]
    });
    shingenIcon = L.marker(shingenLatLng, {icon: shingenIconImage }).addTo(map);
    shingenIcon.bindPopup('発生時刻：'+Time+'<br>最大震度：'+maxIntText+'<br>震源地：'+Name+'<span style=\"font-size: 85%;\"> ('+QuakeJson[num]["earthquake"]["hypocenter"]["latitude"]+", "+QuakeJson[num]["earthquake"]["hypocenter"]["longitude"]+')</span><br>規模：M'+Magnitude+'　深さ：'+Depth+'<br>受信：'+QuakeJson[num]['issue']['time']+', '+QuakeJson[num]['issue']['source'],{closeButton: false, zIndexOffset: 10000, maxWidth: 10000});
    shingenIcon.on('mouseover', function (e) {this.openPopup();});
    shingenIcon.on('mouseout', function (e) {this.closePopup();});

    if (QuakeJson[num]["issue"]["type"] != "ScalePrompt") { //各地の震度に関する情報
        //観測点の震度についてすべての観測点に対して繰り返す
        QuakeJson[num]["points"].forEach(element => {
        var result = JMAPoints.indexOf(element["addr"]);
        if (result != -1) {
            var ImgUrl = "";
            var PointShindo = "";
            if (element["scale"] == 10) {
                ImgUrl = "source/"+icon_theme+"_int1.png";
                PointShindo = "震度1";
            } else if (element["scale"] == 20) {
                ImgUrl = "source/"+icon_theme+"_int2.png";
                PointShindo = "震度2";
            } else if (element["scale"] == 30) {
                ImgUrl = "source/"+icon_theme+"_int3.png";
                PointShindo = "震度3";
            } else if (element["scale"] == 40) {
                ImgUrl = "source/"+icon_theme+"_int4.png";
                PointShindo = "震度4";
            } else if (element["scale"] == 45) {
                ImgUrl = "source/"+icon_theme+"_int50.png";
                PointShindo = "震度5弱";
            } else if (element["scale"] == 46) {
                ImgUrl = "source/"+icon_theme+"_int_.png";
                PointShindo = "震度5弱以上と推定";
            } else if (element["scale"] == 50) {
                ImgUrl = "source/"+icon_theme+"_int55.png";
                PointShindo = "震度5強";
            } else if (element["scale"] == 55) {
                ImgUrl = "source/"+icon_theme+"_int60.png";
                PointShindo = "震度6弱";
            } else if (element["scale"] == 60) {
                ImgUrl = "source/"+icon_theme+"_int65.png";
                PointShindo = "震度6強";
            } else if (element["scale"] == 70) {
                ImgUrl = "source/"+icon_theme+"_int7.png";
                PointShindo = "震度7";
            } else {
                ImgUrl = "source/"+icon_theme+"_int_.png";
                PointShindo = "震度不明";
            }
            if (element["isArea"] == false) { //観測点
                let shindo_latlng = new L.LatLng(JMAPointsJson[result]["lat"], JMAPointsJson[result]["lon"]);
                let shindoIcon = L.icon({
                    iconUrl: ImgUrl,
                    iconSize: [20, 20],
                    popupAnchor: [0, -40]
                });
                let shindoIcon_big = L.icon({
                    iconUrl: ImgUrl,
                    iconSize: [34, 34],
                    popupAnchor: [0, -40]
                });
                shindo_icon = L.marker(shindo_latlng, { icon: shindoIcon,pane: eval('\"shindo'+element["scale"]+'\"') });
                shindo_icon.bindPopup('<ruby>'+element["addr"] + '<rt style="font-size: 0.7em;">' + JMAPointsJson[result]["furigana"] + '</rt></ruby>　'+ PointShindo,{closeButton: false, zIndexOffset: 10000,autoPan: false,});
                shindo_icon.on('mouseover', function (e) {
                    this.openPopup();
                });
                shindo_icon.on('mouseout', function (e) {
                    this.closePopup();
                });
                shindo_layer.addLayer(shindo_icon);

                //塗りつぶしの設定をする
                //AreaNameToCode()は下を参照。大阪府北部を520等に変換
                //filled_listは連想配列で{520: 10, 120: 20}など、エリアコード: 震度の大きさ
                var areaCode = AreaNameToCode(JMAPointsJson[result]["area"]["name"]);
                //filled_listにエリアコードがなかったり、さらに大きな震度になっていたら更新
                if ((!filled_list[areaCode]) || filled_list[areaCode] < element["scale"]) {
                    filled_list[areaCode] = element["scale"];
                }
            }
        }
        });
        //for(... in ...)もforEachと同等。keyに連想配列の名前が入る
        for (key in filled_list){ 
            var PointColor;
            if (filled_list[key] == 10) {
                eval('PointColor = '+icon_theme+'_backColor_1');
            } else if (filled_list[key] == 20) {
                eval('PointColor = '+icon_theme+'_backColor_2');
            } else if (filled_list[key] == 30) {
                eval('PointColor = '+icon_theme+'_backColor_3');
            } else if (filled_list[key] == 40) {
                eval('PointColor = '+icon_theme+'_backColor_4');
            } else if (filled_list[key] == 45) {
                eval('PointColor = '+icon_theme+'_backColor_50');
            } else if (filled_list[key] == 46) {
                eval('PointColor = '+icon_theme+'_backColor_50');
            } else if (filled_list[key] == 50) {
                eval('PointColor = '+icon_theme+'_backColor_55');
            } else if (filled_list[key] == 55) {
                eval('PointColor = '+icon_theme+'_backColor_60');
            } else if (filled_list[key] == 60) {
                eval('PointColor = '+icon_theme+'_backColor_65');
            } else if (filled_list[key] == 70) {
                eval('PointColor = '+icon_theme+'_backColor_7');
            }
            //引数"key"はエリアコード、"PointColor"は塗りつぶし色のHEX値
            FillPolygon(key, PointColor);
        }
    } else { //震度速報
        var icon_theme = "jqk";
        var latlon;
        var latList = [];
        var lonList = [];
        QuakeJson[num]["points"].forEach(element => {
            var ImgUrl = "";
            var PointShindo = "";
            var PointColor;
            if (element["scale"] == 10) {
                eval('PointColor = '+icon_theme+'_backColor_1');
                ImgUrl = "source/"+icon_theme+"_int1.png";
                PointShindo = "震度1";
            } else if (element["scale"] == 20) {
                eval('PointColor = '+icon_theme+'_backColor_2');
                ImgUrl = "source/"+icon_theme+"_int2.png";
                PointShindo = "震度2";
            } else if (element["scale"] == 30) {
                eval('PointColor = '+icon_theme+'_backColor_3');
                ImgUrl = "source/"+icon_theme+"_int3.png";
                PointShindo = "震度3";
            } else if (element["scale"] == 40) {
                eval('PointColor = '+icon_theme+'_backColor_4');
                ImgUrl = "source/"+icon_theme+"_int4.png";
                PointShindo = "震度4";
            } else if (element["scale"] == 45) {
                eval('PointColor = '+icon_theme+'_backColor_50');
                ImgUrl = "source/"+icon_theme+"_int50.png";
                PointShindo = "震度5弱";
            } else if (element["scale"] == 46) {
                eval('PointColor = '+icon_theme+'_backColor_50');
                ImgUrl = "source/"+icon_theme+"_int_.png";
                PointShindo = "震度5弱以上と推定";
            } else if (element["scale"] == 50) {
                eval('PointColor = '+icon_theme+'_backColor_55');
                ImgUrl = "source/"+icon_theme+"_int55.png";
                PointShindo = "震度5強";
            } else if (element["scale"] == 55) {
                eval('PointColor = '+icon_theme+'_backColor_60');
                ImgUrl = "source/"+icon_theme+"_int60.png";
                PointShindo = "震度6弱";
            } else if (element["scale"] == 60) {
                eval('PointColor = '+icon_theme+'_backColor_65');
                ImgUrl = "source/"+icon_theme+"_int65.png";
                PointShindo = "震度6強";
            } else if (element["scale"] == 70) {
                eval('PointColor = '+icon_theme+'_backColor_7');
                ImgUrl = "source/"+icon_theme+"_int7.png";
                PointShindo = "震度7";
            } else {
                eval('PointColor = '+icon_theme+'_backColor__');
                ImgUrl = "source/"+icon_theme+"_int_.png";
                PointShindo = "震度不明";
            }
            var area_Code = AreaNameToCode(element["addr"]);
            latlon = FillPolygon(area_Code, PointColor);
            latList.push(Number(latlon["lat"]));
            lonList.push(Number(latlon["lng"]));
            let shindoIcon = L.icon({
                iconUrl: ImgUrl,
                iconSize: [30, 30],
                popupAnchor: [0, -50]
            });
            var shindo_icon = L.marker(latlon, { icon: shindoIcon,pane: eval('\"shindo'+element["scale"]+'\"') });
            shindo_icon.bindPopup('<ruby>'+element["addr"] + '<rt style="font-size: 0.7em;">' + AreaNameToKana(element["addr"]) + '</rt></ruby>　'+ PointShindo,{closeButton: false, zIndexOffset: 10000,autoPan: false,});
            shindo_icon.on('mouseover', function (e) {
                this.openPopup();
            });
            shindo_icon.on('mouseout', function (e) {
                this.closePopup();
            });
            shindo_layer.addLayer(shindo_icon);
            // console.log(element["addr"] + " " + PointShindo + " OK");
        });
        const aryMax = function (a, b) {return Math.max(a, b);}
        const aryMin = function (a, b) {return Math.min(a, b);}
        var latMax = latList.reduce(aryMax);
        var latMin = latList.reduce(aryMin);
        var lonMax = lonList.reduce(aryMax);
        var lonMin = lonList.reduce(aryMin);
        //通常時の位置初期化の位置
        shingenLatLng = new L.LatLng(Number((latMax+latMin)/2), Number((lonMax+lonMin)/2));
        latList = [];
        lonList = [];
    }
    map.addLayer(shindo_layer);
    map.addLayer(shindo_filled_layer);
    map.flyTo(shingenLatLng, 8, { duration: 0.5 })
}

function AreaNameToCode(Name) {
    var array_Num = AreaName.indexOf(Name);
    return AreaCode[array_Num];
}
function AreaCodeToName(code) {
    var array_Num = AreaCode.indexOf(code);
    return AreaName[array_Num];
}
function AreaNameToKana(Name) {
    var array_Num = AreaName.indexOf(Name);
    return AreaKana[array_Num];
}

function FillPolygon(area_Code, PointColor) {
    var array_Num = AreaCode.indexOf(area_Code);
    if (array_Num != -1) {
        var style;
        style = {
            "color": "#ffffff",
            "weight": 1.2,
            "opacity": 1,
            "fillColor": PointColor,
            "fillOpacity": 1,
        }
        data_japan = japan_data["features"][array_Num];
        var Filled_Layer = L.geoJSON(data_japan, {
            style: style,
            pane: "pane_map_filled",
            onEachFeature: function (feature, layer) {
                if (feature.properties && feature.properties.popupContent) {
                    layer.bindPopup(feature.properties.popupContent);
                }
                layer.myTag = "Filled"
            },
        });
        shindo_filled_layer.addLayer(Filled_Layer);
        let latlon = centerPoint[area_Code];
        return latlon;
    }
}

function hantei_maxIntText(param) {
    let kaerichi = param == 10 ? "1" : param == 20 ? "2" : param == 30 ? "3" : param == 40 ? "4" :
    param == 45 ? "5弱" : param == 46 ? "5弱" : param == 50 ? "5強" : param == 55 ? "6弱" :
    param == 60 ? "6強" : param == 70 ? "7" : "不明";
    return kaerichi;
}
function hantei_Magnitude(param) {
    let kaerichi = param != -1 ? param.toFixed(1) : 'ー.ー';
    return kaerichi;
}
function hantei_Name(param) {
    let kaerichi = param != "" ? param : '情報なし';
    return kaerichi;
}
function hantei_Depth(param) {
    let kaerichi = param != -1 ? "約"+param+"km" : '不明';
    return kaerichi;
}
function hantei_tsunamiText(param) {
    let kaerichi = param == "None" ? "なし" :
    param == "Unknown" ? "不明" :
    param == "Checking" ? "調査中" :
    param == "NonEffective" ? "若干の海面変動" :
    param == "Watch" ? "津波注意報" :
    param == "Warning" ? "津波予報" : "情報なし";
    return kaerichi;
}
```

```css:index.css
html, body, #map {
    width: 100%;
    height: 100%;
    margin: 0;
}
#map {
    background: #1d1d1d;
}

.btns {
    position: absolute;
    bottom: 10px;
    left: 10px;
    z-index: 10000;
    user-select: none;
}
.setsumei, #quakelist, #reload, #map_ichi, #test, #btn_shindo_ichiran, #autoreload, #display_onoff_point_check, #view_info {
    display: inline-block;
    background: #00000088;
    border: white 2.5px solid;
    border-radius: 5px;
    color: white;
    padding: 5px;
    font-family: "ヒラギノ角ゴ-Pro",'Noto Sans JP';
    font-weight: 500;
    font-size: 0.8rem;
    cursor: pointer;
}
#reload, #autoreload {
    background: #00000000;
    padding: 5px;
    border: none;
}
#reload_num, #autoreload_num {
    background: #00000000;
    border: 0;
    width: 2rem;
    font-family: "ヒラギノ角ゴ-Pro",'Noto Sans JP';
    font-weight: 500;
    font-size: 0.9rem;
    color: white;
    border-bottom: #ffffff 1px solid;
}
select, input, button {
    outline: none;
}
input[type="number"]::-webkit-outer-spin-button, 
input[type="number"]::-webkit-inner-spin-button { 
    -webkit-appearance: none; 
    margin: 0; 
} 
input[type="number"] { 
    -moz-appearance:textfield; 
} 
.setsumei {
    cursor: default;
}
#quakelist {
    width: 25em;
    height: calc(2em + 5px);
}

.leaflet-fade-anim .leaflet-popup {
    transition: 0s;
}
.leaflet-popup-content-wrapper {
	background-color: rgba(255, 255, 255, 0.85);
    box-shadow: 0px 3px 7px 2px rgba(0, 0, 0, 0.4);
    border-radius: 0!important;
    font-family: "ヒラギノ角ゴ-Pro",'Noto Sans JP';
    font-weight: 500;
    user-select: none;
	margin-bottom: 0;
}
.leaflet-popup-content {
    color: black !important;
    font-size: 1.2rem;
    margin: 10px 8px 8px 8px;
}
.leaflet-popup-tip-container {
    display: none;
}
```

</details>

## 次回予告
震度分布図を作ろう⑩ ～やること未定～

## 出るかもしれないエラー
:::note alert
`FillPolygon()`の`let latlon = centerPoint[area_Code];`の部分でエラーが出ている人は、
https://nanka.cloudfree.jp/webapps/shindobunpu/source/old/JMAPoints.js と
https://nanka.cloudfree.jp/webapps/shindobunpu/source/old/JMAstations.json
を更新してみてください。
:::