直近の地震があった場所をZENRINMapsAPIで表示してみる

# はじめに

オープンデータの活用と [ZENRIN Maps API](https://developers.zmaps-api.com/) の確認も兼ねて、
日本で発生した地震の震源地を地図に表示してみます

地理座標のあるデータをゼンリンの地図に表示してみたい人向けの記事です

# データソース
直近の地震があった場所は
気象庁が公開している 震源リスト を使用します

https://www.data.jma.go.jp/eqev/data/daily_map/index.html

[執筆時の最新データ](https://www.data.jma.go.jp/eqev/data/daily_map/20250312.html)
※2025年03月12日の震源リスト

公開データはテキストベタ書きなので、取得してCSVにしました
ChatGPTなどでさくっと変換しましょう

```terminal
~ % head csv/20250312.csv
年,月,日,時刻,緯度,経度,深さ(km),M,震央地名
2025,3,12,00:02:46.7,33°4.8'N,130°32.3'E,14.0,0.1,福岡県筑後地方
2025,3,12,00:04:16.2,35°11.0'N,133°5.2'E,13.0,1.5,島根県東部
2025,3,12,00:04:27.1,34°26.1'N,131°34.6'E,28.0,-0.1,山口県北部
2025,3,12,00:04:41.1,28°47.3'N,130°13.5'E,35.0,2.0,奄美大島北東沖
2025,3,12,00:06:6.2,37°22.4'N,136°59.1'E,8.0,1.3,石川県能登地方
2025,3,12,00:06:46.0,34°26.1'N,131°34.8'E,29.0,0.1,山口県北部
2025,3,12,00:09:33.7,36°18.8'N,141°2.9'E,50.0,1.2,茨城県沖
2025,3,12,00:15:47.4,38°22.5'N,142°20.6'E,32.0,1.0,宮城県沖
2025,3,12,00:18:15.0,35°4.4'N,135°42.9'E,9.0,0.5,京都府南部
```

※緯度経度の測地系は世界測地系のようです

# 結果
まずは結果を
![e0a867d58731d765b16e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001795/ae626f63-fd2b-4589-837f-0179c81b57d1.png)

# ZENRIN Maps API
ZENRIN Maps APIで地図を表示するためには APIキー が必要です
登録して取得しておきましょう

[無料トライアルページ](https://www.zenrin-datacom.net/solution/zenrin-maps-api/trial?fm_cp=6757baf3203e3a00bb118509&fm_mu=67639d9aaffc4405037da533&utm_campaign=6757baf3203e3a00bb118509&utm_medium=else&utm_source=Qiita32)
2ヶ月間無料で利用できます


# 構成
```
.
├── index.html
└── csv/
    └── 20250312.csv
```

# コード全体
```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>震源リスト</title>
    <style>
      body {margin: 0; padding: 0;}
      #ZMap {position: absolute; top: 0; bottom: 0; width: 100%;}
    </style>
    <script src="https://test-js.zmaps-api.com/zma_loader.js?key=[APIキー]&auth=referer"></script>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-csv/1.0.40/jquery.csv.min.js" integrity="sha512-Y8iWYJDo6HiTo5xtml1g4QqHtl/PO1w+dmUpQfQSOTqKNsMhExfyPN2ncNAe9JuJUSKzwK/b6oaNPop4MXzkwg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  </head>

  <body>
    <div id="ZMap"></div>

    <script>
      ZMALoader.setOnLoad(function (mapOptions, error) {
        if (error) {
          console.error(error)
          return
        }

        var map;
        const lat = 35.681406, lng = 139.767132;
        const mapElement = document.getElementById('ZMap');
        mapOptions.center = new ZDC.LatLng(lat, lng);

        mapOptions.minZoom = 3;
        mapOptions.maxZoom = 10;
        mapOptions.zoom = 6;

        map = new ZDC.Map(
          mapElement,
          mapOptions,
          function() {
            map.addControl(new ZDC.ZoomButton('top-left'));
            map.addControl(new ZDC.Compass('top-right'));
            map.addControl(new ZDC.ScaleBar('bottom-left'));

            getData().then((datas)=>{
              for(row of datas){
                var widget = new ZDC.Oval(
                    new ZDC.LatLng(row.latitude, row.longitude),
                    {x:5000 , y:5000},
                    {
                      fillPattern: 'none',
                      stroke: '#e34633',
                      strokeWidth: 2,
                      opacity: 0.9
                    }
                  );
                map.addWidget(widget);
              }
            });

          },
          function() {
          }
        );
      })

      const getData = async () => {
        return new Promise(function (resolve, reject) {
          $.ajax({
            type: 'GET',
            url: 'csv/20250312.csv',
            cache: false,
            dataType: 'text',
            success: function(csv){
              const rows = $.csv.toArrays(csv);

              const dms2deg = function(dms){
                return dms[0] + (dms[1] / 60) + (dms[2] / 3600)
              }

              const datas = [];
              rows.forEach((row, idx) => {
                if(idx != 0){
                  lat = row[4].split(/[°\']/);
                  lng = row[5].split(/[°\']/);
                  datas.push(
                    {
                      latitude: dms2deg([Number(lat[0]), Number(lat[1]), 0.0]),
                      longitude: dms2deg([Number(lng[0]), Number(lng[1]), 0.0]),
                      magnitude: Number(row[7])
                    }
                  )
                }
              })
              resolve(datas);
            }
          });
        });
      }
    </script>
  </body>
</html>


```

# 解説

## CSVデータの読み込み
```javascript
      const getData = async () => {
        return new Promise(function (resolve, reject) {
          $.ajax({
            type: 'GET',
            url: 'csv/20250312.csv',
            cache: false,
            dataType: 'text',
            success: function(csv){
              const rows = $.csv.toArrays(csv);

              const dms2deg = function(dms){
                return dms[0] + (dms[1] / 60) + (dms[2] / 3600)
              }

              const datas = [];
              rows.forEach((row, idx) => {
                if(idx != 0){
                  lat = row[4].split(/[°\']/);
                  lng = row[5].split(/[°\']/);
                  datas.push(
                    {
                      latitude: dms2deg([Number(lat[0]), Number(lat[1]), 0.0]),
                      longitude: dms2deg([Number(lng[0]), Number(lng[1]), 0.0]),
                      magnitude: Number(row[7])
                    }
                  )
                }
              })
              resolve(datas);
            }
          });
        });
      }
```
作成した震源リストのCSVデータを非同期で読み込みます
jquery.csv.jsを使用しています


## 緯度経度の単位変換
```javascript
const dms2deg = function(dms){
    return dms[0] + (dms[1] / 60) + (dms[2] / 3600)
}
```

震源リストは 度分秒単位 となっているので、十進法度単位 に換算します
度(°)・分(')・秒(")
※今回の震源リストでは「秒」は使用していないようです

60秒 = 1分、60分 = 1度 なので、それぞれ割った値を加算します

:::note info
十進度 = 度 + (分 ÷ 60) + (秒 ÷ 3600)
:::

今回はjavascript内で変換していますが、事前に変換したCSVデータを用意しても良いでしょう


## 地図上に発生場所を表示
今回はZENRIN Maps APIにある楕円を作成する機能を使用して表示します

https://developers.zmaps-api.com/v20/reference/javascriptAPI/oval.html

```javascript
// 円を作成
var widget = new ZDC.Oval(
    new ZDC.LatLng(row.latitude, row.longitude), // 中心座標
    {x:5000 , y:5000}, // 円の半径メートル ※この場合縦横に半径5000mの円
    {
      fillPattern: 'none', // 円の塗りつぶし
      stroke: '#e34633', // 円の色
      strokeWidth: 2,
      opacity: 0.9
    }
  );
// 作成した円を地図に表示
map.addWidget(widget);
```


# マグニチュードに併せて円のサイズと色を変えてみる

```javascript
switch (true){
  case row.magnitude < 1:
    stroke = '#1b2ade';
    radius = 5000;
    break;
  case 1 <= row.magnitude && row.magnitude < 3:
    stroke = '#1ebc0b';
    radius = 20000;
    break;
  case 3 <= row.magnitude && row.magnitude < 5:
    stroke = '#ded51b';
    radius = 60000;
    break;
  case 5 <= row.magnitude:
    stroke = '#de271b';
    radius = 100000;
    break;
  default:
    stroke = '#e0e7e2';
    radius = 5000;
}

var widget = new ZDC.Oval(
    new ZDC.LatLng(row.latitude, row.longitude),
    {x:radius , y:radius},
    {
      fillPattern: 'none',
      stroke: stroke,
      strokeWidth: 2,
      opacity: 0.9
    }
  );
map.addWidget(widget);
```
switch文やif文で色や半径サイズを指定すればOKです
"深さ"も考慮すると面白いかもしれませんね

![e0a867d58731d765b16e-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001795/ac6c08f5-2b3d-4c61-b553-355ba50bb150.png)

# まとめ
地図座標のあるデータをその場所を地図上に表示することができました
オープンデータの座標を扱う上で気にしておく点は、
- 測地系（世界測地系/日本測地系）
- 座標単位
- 精度（今回で言えは"秒"の有無。同様に小数点以下の桁数も）