震度分布図を作ろう⑤ ～地震を選択可能にしよう～

## はじめ
みなさんこんにちは。
<font color="#aaaaaa">このシリーズもやっと5回まで来ましたが、どうも没になりそうな更新頻度です…
やばいです</font>
↑結局続きを見たい！と思ってくれた方にコメントしていただけるまで全然更新していませんでした…。


### 前回したこと
前回④ではなにをしましたっけ。
**～震源を表示させよう～** なのでマップ上に震源アイコンを表示したんですよね。

### 今回すること
今回することとしては、地震を選択可能にしたいと思います。
![スクリーンショット 2024-02-18 105209.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/d4053f7c-c118-3463-8a25-f6f41a51d38f.png)
↑これ

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

https://qiita.com/kottaro123456/items/d979195a22246e5ba6a2

### ①リストのHTML要素追加
まず、先ほどの画像の、押したらずらっと出てきて選べるやつ(語彙力)のHTML要素を追加しましょう。
<details><summary>ソースコード</summary>

```diff_html:index.html
<body>
    <div id="map"></div>
+   <div class="btns">
+       <select id="quakelist"><option>地震情報の取得中…</option></select>
+   </div>
    <script src="index.js"></script>
</body>
```

↓CSSはすべて追加してください。
バカみたいにクラス名がありますが、後々いるかもしれないので…。

```diff_css:index.css
.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;
}
#quakelist {
    width: 25em;
    height: calc(2em + 5px);
}
```

</details>

![スクリーンショット 2024-02-18 110149.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/654e1deb-8d6c-ac5c-5d5a-8fc4e1c085af.png)
こんな感じでリストができたと思います。

### ②リストの中身全削除
さあ、地震情報追加だー！！と思いますが、まずは「地震情報の取得中…」を消さないと

![スクリーンショット 2024-02-18 110731.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/64f663f5-0b7e-4f78-e2a5-94746a293b2a.png)

となってしまいます。
不細工です。

なので、まずリストの中身を全部消しましょう。

<details><summary>ソースコード</summary>

```diff_javascript:index.js
$.getJSON("https://api.p2pquake.net/v2/history?codes=551", function (data) {
+    while (document.getElementById('quakelist').lastChild) {
+        document.getElementById('quakelist').removeChild(document.getElementById('quakelist').lastChild);
+    }
```

これは、`document.getElementById('quakelist')`の`lastChild`=一番後ろの要素がある限り永遠に繰り返すということで、
一番後ろの要素があれば、`removeChild`で削除しています。

</details>

### ③リストに地震情報を追加
やっと本題です。
地震情報を追加しましょう。

とりあえず、ソースコードを載せます。
```diff_javascript:index.js
while (document.getElementById('quakelist').lastChild) {
    document.getElementById('quakelist').removeChild(document.getElementById('quakelist').lastChild);
}

var forEachNum = 0;
data.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 forEachNum`でこれが何回目のループかを計測しています。あとで使います。

`data.forEach(element => {}`ですが、forEachというのは配列をそれぞれ一回ずつ繰り返してくれる大変便利な関数です。
`element`にその繰り返している配列の中身が格納されます。
`[0, 1, 2, 3]`なら`element = 1`等で、`[["data": 123],["data": 987]]`なら`element = ["data": 123]`等となります。

https://qiita.com/tasty_uni/items/9824a063cad12e78e116

こちらを引用させてもらいます。
とりあえず便利です。何回か試してみて自分で感覚をつかむと配列とかのJSONは無敵です。

`var option`でoptionというHTMLの要素を作ります。
`let maxInt_data = element['earthquake']['maxScale'];`で震度の`10`とか`55`とかを、
`let maxIntText = hantei_maxIntText(maxInt_data);`で`55`とかを`6弱`に直します。

`if (element["issue"]["type"] == "ScalePrompt") {`で震度速報なのか、普通の震度分布なのか[^1]を判定し、適切な文字列に結合します。

`option.value = "" + forEachNum + "";`で、選んだ時に帰ってくる数字`value`の値を設定しています。
`option.textContent = text;`で見える部分のところの文字列を設定しています。
`document.getElementById('quakelist').appendChild(option);`で要素をリストに追加して、
最後に数字を一つ足して、また次へループという感じです。

![スクリーンショット 2024-02-18 112919.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/ed0b6cb5-f6f4-600e-4e8c-ee6a0f29eb3c.png)
いいですね。

### ④選択時に地震情報を再描画
さて、先程まででリストに地震情報を追加し、選択可能になったと思います。
選んだままでは何も起こりませんので、選んだときに地図を更新できるようにしましょう。

```diff_javascript:index.js
    …
        document.getElementById('quakelist').appendChild(option);
        forEachNum++;
    });
    
+    //地震情報リストをクリックしたときの発火イベント
+    var list = document.getElementById('quakelist');
+    list.onchange = event => {
+        QuakeSelect(list.selectedIndex);
+    }

    let maxInt_data = data[0]['earthquake']['maxScale'];
    var maxIntText = hantei_maxIntText(maxInt_data);
    …
```
これでリストを選んだときに（`list.onchange = event => `）、`QuakeSelect();`という関数を発動させることができます。
`QuakeSelect();`という関数はこの下でやります。
また、`QuakeSelect(list.selectedIndex);`の「**<font color="#f00">list.selectedIndex</font>**」というのは、引数（ひきすう）といい、関数に入力したい値を渡すものです。今回では何番目の選択肢を選んだかを知りたいので、リストの`selectedIndex`選ばれた番号を渡しています。
![スクリーンショット 2024-06-29 211415.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/c7a8a780-9458-5cab-08d3-38517afc1fdd.png)

### ⑤再描画の関数を作成
さて、先ほどでリストの選択肢がクリックされたときに、リストの何番目の`num`を含んだ、`QuakeSelect(num)`という関数を発動させる準備ができました。
では`QuakeSelect(num)`を作っていきましょう。

ただし、作ると言っても、前回の④で作成したコードに一部修正を加えるだけです。

```diff_javascript:前回の④で作成したindex.js
    let maxInt_data = data[0]['earthquake']['maxScale'];
    var maxIntText = hantei_maxIntText(maxInt_data);
    var Magnitude = hantei_Magnitude(data[0]['earthquake']['hypocenter']['magnitude']);
    var Name = hantei_Name(data[0]['earthquake']['hypocenter']['name']);
    var Depth = hantei_Depth(data[0]['earthquake']['hypocenter']['depth']);
    var tsunamiText = hantei_tsunamiText(data[0]['earthquake']['domesticTsunami']);
    var Time = data[0]['earthquake']['time'];
    
    var shingenLatLng = new L.LatLng(data[0]["earthquake"]["hypocenter"]["latitude"], data[0]["earthquake"]["hypocenter"]["longitude"]);
    var shingenIconImage = L.icon({
        iconUrl: 'source/shingen.png',
        iconSize: [40, 40],
        iconAnchor: [20, 20],
        popupAnchor: [0, -40]
    });
```

これのどこに修正を加えるかですが、ほとんどのデータを参照するところが`data[0]`で始まっています。
これは、前回では選択できるようにはしていなかったので、最新の地震情報、つまり、JSONの一番初めにくる[^2]地震情報を静的に描画していたことに由来します。
今回では、何番目かを動的に決めるため、`[0]`の部分を、今回の引数である`[num]`に変えればいいだけです。

よってコードはこう変更となります。

```diff_javascript:index.js
+var shingenIcon;
+function QuakeSelect(num) {
+    if (shingenIcon) {
+            map.removeLayer(shingenIcon);
+    }
-    let maxInt_data = data[0]['earthquake']['maxScale'];
+    let maxInt_data = data[num]['earthquake']['maxScale'];
    var maxIntText = hantei_maxIntText(maxInt_data);
    var Magnitude = hantei_Magnitude(data[num]['earthquake']['hypocenter']['magnitude']);
    var Name = hantei_Name(data[num]['earthquake']['hypocenter']['name']);
    var Depth = hantei_Depth(data[num]['earthquake']['hypocenter']['depth']);
    var tsunamiText = hantei_tsunamiText(data[num]['earthquake']['domesticTsunami']);
    var Time = data[num]['earthquake']['time'];
    
    var shingenLatLng = new L.LatLng(data[num]["earthquake"]["hypocenter"]["latitude"], data[num]["earthquake"]["hypocenter"]["longitude"]);
    var shingenIconImage = L.icon({
        iconUrl: 'source/shingen.png',
        iconSize: [40, 40],
        iconAnchor: [20, 20],
        popupAnchor: [0, -40]
    });
-    var shingenIcon = L.marker(shingenLatLng, {icon: shingenIconImage }).addTo(map);
+    shingenIcon = L.marker(shingenLatLng, {icon: shingenIconImage }).addTo(map);
    shingenIcon.bindPopup('発生時刻：'+Time+'<br>最大震度：'+maxIntText+'<br>震源地：'+Name+'<span style=\"font-size: 85%;\"> ('+data[num]["earthquake"]["hypocenter"]["latitude"]+", "+data[num]["earthquake"]["hypocenter"]["longitude"]+')</span><br>規模：M'+Magnitude+'　深さ：'+Depth+'<br>受信：'+data[num]['issue']['time']+', '+data[num]['issue']['source'],{closeButton: false, zIndexOffset: 10000, maxWidth: 10000});
    shingenIcon.on('mouseover', function (e) {this.openPopup();});
    shingenIcon.on('mouseout', function (e) {this.closePopup();});
+}
```
今までの地震情報を描画していたところを`QuakeSelect(num)`で囲み、簡単に実行できるようにしたうえで、`[0]`を`[num]`に変えるだけです。
また、新たな問題として、`.addTo(map)`して地図に追加していくだけだと、前の震源アイコンが削除されずに複数のアイコンが地図上に乗ってしまいますので、`map.removeLayer(shingenIcon);`で`shingenIcon`を削除しています。

![スクリーンショット 2024-06-29 214242 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/9467fc60-a8a8-5007-7c5c-7975c446f4b1.png)
これで、リストから選択した地震情報を地図に描画できるようになりました！

## 終わりに
だいぶ空いてしまったこのシリーズですが、今回はリストから選択した地震情報を描画できるところまでやりました。
次回ではちょっとした改善と情報を更新できるようにしましょう。

今回のサンプル
https://nanka.cloudfree.jp/bin/webapps/shindobunpu_qiita/5/

<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">
    <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="index.css">
</head>
<body>
    <div id="map"></div>
    <div class="btns">
        <select id="quakelist"><option>地震情報の取得中…</option></select>
    </div>
    <script src="index.js"></script>
</body>
</html>
```

```javascript:index.js
var map = L.map('map').setView([36.575, 137.984], 6);
L.control.scale({ maxWidth: 150, position: 'bottomright', imperial: false }).addTo(map);
map.zoomControl.setPosition('topright');

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

$.getJSON("prefectures.geojson", function (data) {
    L.geoJson(data, {
        style: PolygonLayer_Style_nerv
    }).addTo(map);
});

$.getJSON("https://api.p2pquake.net/v2/history?codes=551&limit=50", function (data) {
    while (document.getElementById('quakelist').lastChild) {
        document.getElementById('quakelist').removeChild(document.getElementById('quakelist').lastChild);
    }

    var forEachNum = 0;
    data.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 list = document.getElementById('quakelist');
    list.onchange = event => {
        QuakeSelect(list.selectedIndex);
    }

    var shingenIcon;
    function QuakeSelect(num) {
        if (shingenIcon) {
            map.removeLayer(shingenIcon);
        }
        let maxInt_data = data[num]['earthquake']['maxScale'];
        var maxIntText = hantei_maxIntText(maxInt_data);
        var Magnitude = hantei_Magnitude(data[num]['earthquake']['hypocenter']['magnitude']);
        var Name = hantei_Name(data[num]['earthquake']['hypocenter']['name']);
        var Depth = hantei_Depth(data[num]['earthquake']['hypocenter']['depth']);
        var tsunamiText = hantei_tsunamiText(data[num]['earthquake']['domesticTsunami']);
        var Time = data[num]['earthquake']['time'];
        
        var shingenLatLng = new L.LatLng(data[num]["earthquake"]["hypocenter"]["latitude"], data[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%;\"> ('+data[num]["earthquake"]["hypocenter"]["latitude"]+", "+data[num]["earthquake"]["hypocenter"]["longitude"]+')</span><br>規模：M'+Magnitude+'　深さ：'+Depth+'<br>受信：'+data[num]['issue']['time']+', '+data[num]['issue']['source'],{closeButton: false, zIndexOffset: 10000, maxWidth: 10000});
        shingenIcon.on('mouseover', function (e) {this.openPopup();});
        shingenIcon.on('mouseout', function (e) {this.closePopup();});
    }
});

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;
}
#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>

## 次回予告
震度分布図を作ろう⑥ ～情報を更新できるようにしよう～

https://qiita.com/kottaro123456/items/8227eadf13da323ed6ec


[^1]:震度速報とは、速報とある通り、震源の位置と大まかな地域ごとの最大震度を地震発生後すぐに発表するものです。"ScalePrompt"がP2P地震情報の震度速報だよ！という合図です。 
[^2]:JavaScriptの配列や連想配列では、一番はじめを`0`番目で始めます。よって、例として3番めの情報を呼び出す際は`data[2]`であることに注意してください。