震度分布図を作ろう④ ～震源を表示させよう～

## はじめ
みなさんこんにちは。
もう初めのあいさつはネタ切れなのでしません。

### 前回したこと
前回は、一旦Leafletから離れて地震情報を取得、整理して表示させるところまでしましたね。
JavaScriptでのJSONの触り方もしましたね。

### 今回
今回は、さっそく地震情報を使って震源の位置を地図に表示させていきたいと思います。

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

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

の一番下に乗っていますので、コピーして使用ください。

### 今回の開発は
では今回することを見ていきましょう。
今回は
①地震情報の整理
②地図に表示
(③震源情報をポップアップに追加)
をしようと思っています。
③はオプションです。

では①の地震情報の整理からやっていきましょう。

### ①地震情報の整理
は？前やったやないか。と思いますが、今回は震源の位置を扱っていくので、前とは違い、緯度・経度の情報を取得する必要があります。

ではさっそく取得してみましょう。

緯度は英語で「latitude」、経度は「longitude」というようです。
いいのがあったので拝借しておきます。

:::note info
LongitudeはLongとあるように数値的には Lat（緯度90度まで）よりは長い方です。
:::

なるほど、たしかに経度は180度まであるので長い方ですね。
ただ、緯度経度は一緒に扱うので、特にどっちがどっちと区別する必要はありません…。
**それよりも英語の短縮形での緯度経度「LatLng」のほうがよく使うのでこっちのほうが必要です。**

地震情報から緯度経度の情報を取得しましょう。

地震情報の中から「latitude」「longitude」は…

```javascript:地震情報(③を参照)
[
  {
    …
    "earthquake": {
        "latitude": 34.5, //緯度
        "longitude": 140.3, //経度
        …
      },
      …
    },
    …
]
```

ありましたね。
日本の経度は東経135°、緯度は北緯35°くらいなので、あっていますね。

取得してみましょう。
今回は緯度経度情報を`new L.LatLng()`というのに入れて扱います。

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

```diff_javascript:index.js
$.getJSON("https://api.p2pquake.net/v2/history?codes=551", function (data) {
    var [time, name, shindo, magnitude, depth] = [
        data["0"]["earthquake"]["time"],
        data["0"]["earthquake"]["hypocenter"]["name"],
        data["0"]["earthquake"]["maxScale"],
        data["0"]["earthquake"]["hypocenter"]["magnitude"],
        data["0"]["earthquake"]["hypocenter"]["depth"]
    ]
    console.log(time+"ごろ、"+name+"で最大震度"+shindo/10+"の地震が発生しました。マグニチュードは"+magnitude+"、深さ"+depth+"kmと推定されています。");

+    var shingenLatLng = new L.LatLng(data[0]["earthquake"]["hypocenter"]["latitude"], data[0]["earthquake"]["hypocenter"]["longitude"]);
```

`new L.LatLng()`というのは、Leafletで緯度経度情報を扱うときに使うコンテナです。
直接緯度経度をマーカーに指定することもできますが、簡略化のため今回はこれを使います。

</details>

これで震源情報を取得することができました。

### ②地図に表示
次は震源を地図に表示してみましょう。

まず、震源のアイコンを持ってきます。
![shingen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/b4a90f43-d844-fd77-8f28-221910d52e3b.png)
↑を使用してもいいですし、自分で適当なものを持ってきても構いません。
震源アイコンを適当な位置にダウンロードします。

ではどうやって震源アイコンを配置するかですが、一番最初？にマーカーを設置しましたよね。

```javascript:①より
L.marker([51.5, -0.09]).addTo(map)
    .bindPopup('A pretty CSS popup.<br> Easily customizable.')
    .openPopup();
```

これです。これのビジュアルにこの震源のアイコンを設定してあげるだけです。

では設定していきましょう。

#### アイコン画像の設定

まずは震源アイコンのアイコンの部分の設定です。

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

```diff_javascript:index.js
$.getJSON("https://api.p2pquake.net/v2/history?codes=551", function (data) {
    var [time, name, shindo, magnitude, depth] = [
        data["0"]["earthquake"]["time"],
        data["0"]["earthquake"]["hypocenter"]["name"],
        data["0"]["earthquake"]["maxScale"],
        data["0"]["earthquake"]["hypocenter"]["magnitude"],
        data["0"]["earthquake"]["hypocenter"]["depth"]
    ]
    console.log(time+"ごろ、"+name+"で最大震度"+shindo/10+"の地震が発生しました。マグニチュードは"+magnitude+"、深さ"+depth+"kmと推定されています。");

    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]
+   });
```

`L.icon`でLeafletのアイコンの設定をします。
`iconUrl`でアイコンへのパスを設定します。
`iconSize`でアイコンのサイズをピクセル単位で設定します。
`iconAnchor`でアイコンのどこをプロットするかをピクセル単位で設定します。
[Stack Overflow](https://stackoverflow.com/questions/46101450/explanation-of-leaflet-custom-icon-latlng-vs-xy-coordinates) の図説がわかりやすいですかね。

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

<font color="#ff0000">**↑一番わかり易い解説書きました。**</font>

`popupAnchor`でアイコンのどこからポップアップを出すかをピクセル単位で設定します。

</details>

#### アイコンを表示

アイコンの画像の設定は終わったのでさっそくアイコンを表示させていきましょう。

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

```diff_javascript:index.js
$.getJSON("https://api.p2pquake.net/v2/history?codes=551", function (data) {
    var [time, name, shindo, magnitude, depth] = [
        data["0"]["earthquake"]["time"],
        data["0"]["earthquake"]["hypocenter"]["name"],
        data["0"]["earthquake"]["maxScale"],
        data["0"]["earthquake"]["hypocenter"]["magnitude"],
        data["0"]["earthquake"]["hypocenter"]["depth"]
    ]
    console.log(time+"ごろ、"+name+"で最大震度"+shindo/10+"の地震が発生しました。マグニチュードは"+magnitude+"、深さ"+depth+"kmと推定されています。");

    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]
    });
+   var shingenIcon = L.marker(shingenLatLng, {icon: shingenIconImage }).addTo(map);
});
```

`L.marker`でLeafletのマーカーの設定をします。
()の中のshingenLatLngでアイコンを置く緯度経度を指定します。
shingenLatLngのあとの{}の中でオプションを指定します。今回は独自アイコンのスタイルを設定しています。

</details>

#### 動作確認
ではサーバーにアップロードして動作確認です。

執筆時点では地震情報は「千葉県東方沖」なので千葉の右側にあったらOKです。

![s-20231222010413.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/f07e357e-cfd6-21c7-20a0-303dd5b2401a.jpeg)

お、いいところに震源アイコンが刺さっています。
これで震源アイコンのプロットは完了です。

### (③震源情報をポップアップに追加)

:::note warn
これはオプションです。
やってもやらなくてもいいです。
:::

さて、プロットして完了でもいいのですが、せっかくなら地震情報を見てみたいと思いませんか？
そんなときに便利なのが「Popup」や「Tooltip」です。
「Popup」はクリックしたときに上に出てくるやつ、
「Tooltip」はホバー(マウスを重ねる)したときに出てくるやつです。
どちらもアイコンの詳細情報を追加するときに使われます。

![20231222015247.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637649/48a21e47-e84c-ed7f-8866-2833d7e33175.jpeg)

こんなん。

これを実装してみましょう。
今回はポップアップで出します。
ただ、クリックするのは嫌なので、ホバーしたときに出てくるようにします。

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

```diff_javascript:index.js
$.getJSON("https://api.p2pquake.net/v2/history?codes=551", function (data) {
    let maxInt_data = data[0]['earthquake']['maxScale'];
    var maxIntText = maxInt_data == 10 ? "1" : maxInt_data == 20 ? "2" : maxInt_data == 30 ? "3" : maxInt_data == 40 ? "4" :
                     maxInt_data == 45 ? "5弱" : maxInt_data == 46 ? "5弱" : maxInt_data == 50 ? "5強" : maxInt_data == 55 ? "6弱" :
                     maxInt_data == 60 ? "6強" : maxInt_data == 70 ? "7" : "不明";
    
    var Magnitude = data[0]['earthquake']['hypocenter']['magnitude'] != -1 ?
                    (data[0]['earthquake']['hypocenter']['magnitude']).toFixed(1) : 'ー.ー';
    var Name = data[0]['earthquake']['hypocenter']['name'] != "" ?
               data[0]['earthquake']['hypocenter']['name'] : '情報なし';
    var Depth = data[0]['earthquake']['hypocenter']['depth'] != -1 ?
                "約"+data[0]['earthquake']['hypocenter']['depth']+"km" : '不明';
    var tsunamiText = data[0]['earthquake']['domesticTsunami'] == "None" ? "なし" :
                      data[0]['earthquake']['domesticTsunami'] == "Unknown" ? "不明" :
                      data[0]['earthquake']['domesticTsunami'] == "Checking" ? "調査中" :
                      data[0]['earthquake']['domesticTsunami'] == "NonEffective" ? "若干の海面変動" :
                      data[0]['earthquake']['domesticTsunami'] == "Watch" ? "津波注意報" :
                      data[0]['earthquake']['domesticTsunami'] == "Warning" ? "津波警報" : "情報なし";
    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]
    });
    var shingenIcon = L.marker(shingenLatLng, {icon: shingenIconImage }).addTo(map);
    shingenIcon.bindPopup('発生時刻：'+Time+'<br>最大震度：'+maxIntText+'<br>震源地：'+Name+'<span style=\"font-size: 85%;\"> ('+data[0]["earthquake"]["hypocenter"]["latitude"]+", "+data[0]["earthquake"]["hypocenter"]["longitude"]+')</span><br>規模：M'+Magnitude+'　深さ：'+Depth+'<br>受信：'+data[0]['issue']['time']+', '+data[0]['issue']['source'],{closeButton: false, zIndexOffset: 10000, maxWidth: 10000});
    shingenIcon.on('mouseover', function (e) {this.openPopup();});
    shingenIcon.on('mouseout', function (e) {this.closePopup();});
});
```

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

.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;
}
```

最初の方で震度やマグニチュードなどの判別をしています。
`shingenIcon.bindPopup();`でアイコンにポップアップを追加しています。
`shingenIcon.on('mouseover', function (e) {this.openPopup();});`でマウスホバーしたときにポップアップを開くことをしています。
ポップアップの内容は自分でさらにカスタマイズすることができます。

</details>

## 終わりに
さて、今回は地震情報から震源を地図に表示し、ポップアップを表示するところまで終わりました。
次回には、観測点を表示する前に、どの地震を描画するかの選択ができるように機能を追加していきましょう。

今回のデモ
https://nanka.cloudfree.jp/bin/webapps/shindobunpu_qiita/4/

<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>
    <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", function (data) {
    let maxInt_data = data[0]['earthquake']['maxScale'];
    var maxIntText = maxInt_data == 10 ? "1" : maxInt_data == 20 ? "2" : maxInt_data == 30 ? "3" : maxInt_data == 40 ? "4" :
                     maxInt_data == 45 ? "5弱" : maxInt_data == 46 ? "5弱" : maxInt_data == 50 ? "5強" : maxInt_data == 55 ? "6弱" :
                     maxInt_data == 60 ? "6強" : maxInt_data == 70 ? "7" : "不明";
    
    var Magnitude = data[0]['earthquake']['hypocenter']['magnitude'] != -1 ?
                    (data[0]['earthquake']['hypocenter']['magnitude']).toFixed(1) : 'ー.ー';
    var Name = data[0]['earthquake']['hypocenter']['name'] != "" ?
               data[0]['earthquake']['hypocenter']['name'] : '情報なし';
    var Depth = data[0]['earthquake']['hypocenter']['depth'] != -1 ?
                "約"+data[0]['earthquake']['hypocenter']['depth']+"km" : '不明';
    var tsunamiText = data[0]['earthquake']['domesticTsunami'] == "None" ? "なし" :
                      data[0]['earthquake']['domesticTsunami'] == "Unknown" ? "不明" :
                      data[0]['earthquake']['domesticTsunami'] == "Checking" ? "調査中" :
                      data[0]['earthquake']['domesticTsunami'] == "NonEffective" ? "若干の海面変動" :
                      data[0]['earthquake']['domesticTsunami'] == "Watch" ? "津波注意報" :
                      data[0]['earthquake']['domesticTsunami'] == "Warning" ? "津波警報" : "情報なし";
    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]
    });
    var shingenIcon = L.marker(shingenLatLng, {icon: shingenIconImage }).addTo(map);
    shingenIcon.bindPopup('発生時刻：'+Time+'<br>最大震度：'+maxIntText+'<br>震源地：'+Name+'<span style=\"font-size: 85%;\"> ('+data[0]["earthquake"]["hypocenter"]["latitude"]+", "+data[0]["earthquake"]["hypocenter"]["longitude"]+')</span><br>規模：M'+Magnitude+'　深さ：'+Depth+'<br>受信：'+data[0]['issue']['time']+', '+data[0]['issue']['source'],{closeButton: false, zIndexOffset: 10000, maxWidth: 10000});
    shingenIcon.on('mouseover', function (e) {this.openPopup();});
    shingenIcon.on('mouseout', function (e) {this.closePopup();});
});
```

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

.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/99bdc867ae7e54a90acb