Google Maps JavaScript APIで複数マーカーをいい感じに表示する

[Google Maps PlatformのMaps JavaScript API](https://cloud.google.com/maps-platform/?hl=ja)を使う機会があったのですが､とても簡単に複数マーカーをいい感じに表示できたので備忘録も兼ねて記事にしたいと思います｡
ソースは[ここ](https://gist.github.com/KeisukeKudo/8c1eb8a1eb53323860dea4cea0bb79f2)

なお､API Keyは取得していることを前提として進めます｡
取得がまだの方は[ここ](https://developers.google.com/maps/documentation/javascript/get-api-key?hl=ja)を参考にしてみてください｡

# Google Mapを表示させる
基本的に[ドキュメント](https://developers.google.com/maps/documentation/javascript/tutorial?hl=ja)を読めば簡単に表示させられます｡

```html
<div id="map"><!-- Google Map表示 --></div>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"></script>
<script>
  function initMap() {
    // 日本の中央を表示する
    const defaultSettings = {zoom: 15, center: {lat: 40.804365, lng: 141.134867}};
    const map = new google.maps.Map(
      document.querySelector('#map'),
      defaultSettings
    );
  }
</script>
```
- Map表示用の`div`を用意
- Maps JavaScript APIを読み込む(API keyを忘れずに)
- callback関数の`initMap`を定義
- Map表示用`div`の`DOM`とズームレベルや初期表示位置を設定

これだけ
API Key取得のほうが面倒ですね｡

# 指定の場所にマーカーをつける
こちらも簡単です｡
[ドキュメント](https://developers.google.com/maps/documentation/javascript/markers?hl=ja)

```javascript
// 表示データ
const data = [
  { name: "東京タワー", lat: 35.6585805, lng: 139.7454329 },
  { name: "京都タワー", lat: 34.9875441, lng: 135.7592164 },
  { name: "通天閣", lat: 34.6524992, lng: 135.5063058 }
];

function initMap() {
  // 省略

  // 現在表示されているinfoWindowオブジェクト
  let currentWindow;

  data.map(d => {
    // マーカーをつける
    const marker = new google.maps.Marker({
      position: {lat: d.lat, lng: d.lng},
      map: map
    });

    // マーカークリックしたら地名をポップアップさせる
    marker.addListener('click', () => {
      currentWindow && currentWindow.close();
      const infoWindow = new google.maps.InfoWindow({content: d.name});
      infoWindow.open(map, marker);
      currentWindow = infoWindow;
    });
  });
}
```
jsonを`map`で回して`Marker`オブジェクトを作ります｡

せっかくなので､マーカーをクリックしたらその地名が表示されるようにました｡

- `Marker`オブジェクトそれぞれに`click`イベントを付与
- 既に開いている`infoWindow`オブジェクトがあればそのwindowを閉じる
- クリックされたマーカーの`infoWindow`オブジェクトを作成してwindowを開く
- 開いた`infoWindow`オブジェクトを保存

`infoWindow`オブジェクトを保持するのがちょっと不格好ですが､これ以外に方法がなさそうでした｡
開いているwindowを取得できればいいのですが......

# マーカーが画面上に収まるようにする
マーカーを表示できたのはいいのですが､画面に収まっていません｡
そんなときは`fitBounds`メソッドを使います｡
引数には､`LatLngBounds`クラスで範囲のインスタンスを渡してあげます｡
`LatLngBounds`の引数には緯度経度の最小値､最大値を渡します｡

```javascript
// マーカーがいい感じに表示できるよう調整
map.fitBounds(new google.maps.LatLngBounds(
  // sw
  {
    lat: Math.min(...data.map(d => d.lat)),
    lng: Math.min(...data.map(d => d.lng))
  },
  // ne
  {
    lat: Math.max(...data.map(d => d.lat)),
    lng: Math.max(...data.map(d => d.lng))
  }
));
```
ここで重要なの`Map`オブジェクトを作った際に指定した`maxZoom`オプションです｡

```javascript
function initMap() {
  // 日本の中央を表示する
  const defaultSettings = {zoom: 15, /* これ */ maxZoom: 15, center: {lat: 40.804365, lng: 141.134867}};;
  const map = new google.maps.Map(
    document.querySelector('#map'),
    defaultSettings
  );

  // 以下省略
}
```
このオプションがないと､マーカーが1つだったり､マーカー同士が極端に近い場合､拡大されすぎて見栄えが悪くなってしまうんです｡

こんな感じに
![2019-04-27-17-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74711/9e309025-31c8-11b0-922d-fcd96a7bfb2d.png)

# 動的に拡大率を変更する方法
基本的に`fitBounds`や`maxZoom`オプションで事足りると思いますが､何らかの条件をつけて拡大率を動的に変更しなければならない場合は以下のうようにします｡

```javascript
const listener = google.maps.event.addListener(map, 'idle', () => {
  // 実際には何らかの条件で拡大率を取得設定する
  map.getZoom() > 15 && map.setZoom(15);
  google.maps.event.removeListener(listener);
});
```
Mapのイベントに`addListener`してズームレベルを変えています｡
なぜこんな面倒なことをしなければならないのか
実は`fitBounds`を実行したあとは､`setZoom()`も`getZoom()`も効かないのです｡
[how do I get the zoom level after fitBounds](https://groups.google.com/forum/#!topic/google-maps-js-api-v3/SKSbFIGutkw)

`google.maps.event`であれば何でもよさそうだったので､様々な処理が完了したタイミングで実行される`idle`イベントで処理するようにしました｡

バグなのか仕様なのかはわかりかねますが､拡大率が設定できない場合は､上記のうようにしてみてください｡

# 参考
[Google Maps Platform Documentation](https://developers.google.com/maps/documentation/javascript/tutorial)