Google Mapsに複数のマーカーと情報ウィンドウを表示


#はじめに
Google Maps Platformを利用してGoogle Mapsに複数のマーカーと情報ウィンドウを表示する方法をまとめました。
テーマは「javascript初心者によるjavascript初心者のための解説」です。丁寧すぎたかも。

#前提

Maps JavaScript API と Geocoding API のAPI KEYを取得している前提で進めます。
未取得の方は下記などを参考にしてみてください。

 - [Google Maps API を使ってみた](https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037 "Google Maps API を使ってみた")
 - [Get an API Key(Google Maps Platform)](https://developers.google.com/maps/documentation/javascript/get-api-key?hl=ja "Get an API Key")

##バージョン
ruby 2.6.3
Rails 6.0.3.3
※Rails5以前と6でJavaScript周りの仕様が変わったらしいので、Rails5の方はお気をつけください。

   - 参考:[Rails6でJavaScriptを使う方法(Qiita)](https://qiita.com/shuhei_takada/items/99c616029e75af8f3fc0 "Rails6でJavaScriptを使う方法")

#成果物 
以下のように、画面内に複数のマーカーがあり、マーカーをクリックすると店名を表示するGoogle Mapsを作成しました。

![スクリーンショット 2020-12-03 1.58.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695176/72f160a0-0af9-4e13-0bf5-8fc98c6af264.png)


#コード
```googlemaps.html.erb
<div id='map' class="googlemaps"></div>

<%= javascript_pack_tag "googlemaps/application" %>
<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLEMAPS_API_KEY"]%>&callback=initMap&libraries=places&v=weekly" async defer ></script>

```
```googlemaps.css
#map {
  width: 600px;
  height: 300px;
}
```

```googlemaps.js

window.initMap = () => {}

window.onload = function() {
  const addresses = [...document.querySelectorAll("#mapAddress")].map((node) => node.textContent);
  const shopNames = [...document.querySelectorAll("#shop_name_js")].map((node) => node.textContent);
  let data = shopNames.map(function(name, index) {
    return {
      name: name,
      address: addresses[index]
    }
  })

  const googleMapElement = document.getElementById('map');
  const infowindow = new google.maps.InfoWindow();

  if (addresses.length > 0) {
    window.initMap = mappingPinToGoogleMap(addresses, googleMapElement)
  }


function mappingPinToGoogleMap(addresses, googleMapElement) {
  const googleMap = new google.maps.Map(googleMapElement, {
    zoom: 16,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  });

  const bounds = new google.maps.LatLngBounds();

  data.forEach((data) => {

    const geocoder = new google.maps.Geocoder();
    geocoder.geocode({ 'address': data.address, 'region': 'jp' }, (result, status) => {
      if(status == google.maps.GeocoderStatus.OK) {

        const lat = result[0].geometry.location.lat();
        const lng = result[0].geometry.location.lng();
        const latlng = {lat,lng};

        const marker = new google.maps.Marker({
          position: new google.maps.LatLng(latlng),
          map: googleMap,
        });   

        if (addresses.length > 1) {
          bounds.extend(marker.position);
          googleMap.fitBounds(bounds,20);
          const infowindow = new google.maps.InfoWindow({
            content: data.name,
          });
          marker.addListener("click", () => {
            infowindow.open(googleMap, marker);
          });
        } else {
          googleMap.setCenter(latlng);
        }
      }
    })
  })
}
}
```

#解説
##GoogleMapsを表示
```javascript

const googleMapElement = document.getElementById('map');
```
```javascript

function mappingPinToGoogleMap(addresses, googleMapElement) {
  const googleMap = new google.maps.Map(googleMapElement, {
    zoom: 16,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  });
}
```
- 住所の配列とgoogleMapのHTML要素を受け取ってマッピングします。
 - 参考:[Maps JavaScript API(Google Maps Platform)](https://developers.google.com/maps/documentation/javascript/overview "Maps JavaScript API")
- mapTypeId: google.maps.MapTypeId.ROADMAP => マップタイプを指定します。ROADMAPは道路や建物などが表示される標準的な地図です。以下の公式にそのほかのタイプも記載されています。
 -  [Map Types(Google Maps Platform)](https://developers.google.com/maps/documentation/javascript/maptypes "Map Types")

##住所が存在するか判定してマップを表示
保守性とページの読み込み速度を保つため、住所があるページのみでGoogleMapsを表示します。

```javascript
  // 住所が存在するか判定してマップを表示
  if (addresses.length > 0) {
    window.initMap = mappingPinToGoogleMap(addresses, googleMapElement)
  }
```

##要素を取得
ページ上の店名と住所をそれぞれ配列で取得 => 店名と住所をハッシュ化
住所に対応した情報ウィンドウを出すために、店名と住所を紐付けます。

```javascript

  // 住所を取得
  const addresses = [...document.querySelectorAll("#mapAddress")].map((node) => node.textContent);
  // 店名を取得
  const shopNames = [...document.querySelectorAll("#shop_name_js")].map((node) => node.textContent);
  // 取得した住所と店名をハッシュ化
  let data = shopNames.map(function(name, index) {
    return {
      name: name,
      address: addresses[index]
    }
  })
```

- document.querySelectorAll("#mapAddress") => 複数の要素を取得
 - 参考 : [querySelectorAll(MDN web docs)](https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll "Document.querySelectorAll()")
- .textContent => テキストのみを取得
 - 参考 : [.textContent(MDN web docs)](https://developer.mozilla.org/ja/docs/Web/API/Node/textContent "Node.textContent")
- .map((node) => node.textContent); => 取得した全ての要素からテキストを呼び出して新しい配列を作成
 - 参考 : [.map(MDN web docs)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map "Array.prototype.map()")
- […  ] => NodeListをArray(配列)に変換
 - 参考 :[\[…  \](hitode909の日記)](https://blog.sushi.money/entry/2017/04/19/114028)

##住所から座標を取得(Geocoding/ジオコーディング)

マーカーを表示するためには、住所を緯度・経度に変換する必要があります。

```javascript

  //ジオコードオブジェクトを定義
  const geocoder = new google.maps.Geocoder();
  geocoder.geocode({ 'address': data.address, 'region': 'jp' }, (result, status) => {

  if(status == google.maps.GeocoderStatus.OK) {
    const lat = result[0].geometry.location.lat();
    const lng = result[0].geometry.location.lng();
    const latlng = {lat,lng};
  })
```
 - geocoder.geocode(){(... => リクエストを送信します。コールバック関数の引数resultsとstatusを利用して処理します。
  - 参考:[Geocoding API(Google Maps Platform)](https://developers.google.com/maps/documentation/geocoding/overview "Geocoding API")
 - status == google.maps.GeocoderStatus.OK => ジオコーディングが機能している場合のみ{}内の処理をします。
  - 参考:[ジオコーディング(マッピィ)](https://www.google-mapi.com/event-data-processing/geocoding/ "ジオコーディング")
 - result[0].geometry.location => latitude(緯度)とlongitude(経度)を取得します。
  - 参考:[latLngオブジェクトからlat lngをとりだす。(空飛ぶヒヤヤッコ)](https://nago75.hatenadiary.org/entry/20110203/1296763001 "latLngオブジェクトからlat lngをとりだす。")

## マーカーを立てる場所の指定
取得したLatlang(緯度・経度)の地点にマーカーを表示します。

```javascript

  const marker = new google.maps.Marker({
    position: new google.maps.LatLng(latlng),
    map: googleMap,
  });   
```

- google.maps.Marker({... => Markerオブジェクトを作成します。プロパティはpositionのみ必須です。そのほかは下記などを参考にしてください。
 - [Markers(Google Maps Platform)](https://developers.google.com/maps/documentation/javascript/markers "Markers")
 - [マーカー(マッピィ)](https://www.google-mapi.com/overlay/marker/ "マーカー")

##複数のマーカーを画面内に表示


```javascript

  // bounds(地図の境界)を定義
  const bounds = new google.maps.LatLngBounds();
```
```javascript

  bounds.extend(marker.position);
  googleMap.fitBounds(bounds,20);
```
- bounds.extend(marker.position) => 地図表示領域をマーカー位置に合わせて拡大
 - 参考:[LatLngBounds.extend()(syncer)](https://lab.syncer.jp/Web/API/Google_Maps/JavaScript/LatLngBounds/extend/ "LatLngBounds.extend()")
- googleMap.fitBounds(bounds,20) => マーカーが画面内に収まるように調整。第二引数は省略可。大きい数字:広域、小さい数字:詳細 な地図になります。
 - 参考:[Map.fitBounds()(syncer)](https://lab.syncer.jp/Web/API/Google_Maps/JavaScript/Map/fitBounds/ "Map.fitBounds()")

##特定のマーカーのみを表示

```javascript

googleMap.setCenter(latlng);
```
- .setCenter => 特定のマーカーを中心に表示します。複数のマーカーを画面内に表示するメソッドを使用すると、表示が詳細に寄ってしまい調整できなかったので処理を分けました。
 - 参考:[Map.setCenter()(Syncer)](https://lab.syncer.jp/Web/API/Google_Maps/JavaScript/Map/setCenter/ "Map.setCenter()")


##情報ウィンドウ(infowindow)を表示
```javascript

  // infowindowを定義
  const infowindow = new google.maps.InfoWindow();
```

```javascript

  // infoWindowの表示設定
  const infowindow = new google.maps.InfoWindow({
    content: data.name,
  });
  marker.addListener("click", () => {
    infowindow.open(googleMap, marker);
  });
```
- content: data.name => 表示内容を指定できます。そのほか、maxWidth(infowindowの幅)などを指定できます。下記公式に載っていますので必要に応じて参考にしてください。
  - 参考:[Info Windows(Google Maps Platform)](https://developers.google.com/maps/documentation/javascript/infowindows "Info Windows")
- marker.addListener("click", () => { ... => クリックするとinfowindowを表示します。
  - 参考:[【JavaScript入門】addEventListener()によるイベント処理の使い方！(Samurai Blog)](https://www.sejuku.net/blog/57625)
 - 参考:[MediaQueryList.addListener()(MDN web docs)](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener "MediaQueryList.addListener()")

#おわりに


誤字脱字やコードのアドバイスなどありましたらコメントいただけると嬉しいです。

Google Maps Platformは[公式ドキュメント](https://developers.google.com/maps/documentation/?hl=ja&_ga=2.168435301.557368631.1606911299-684597306.1603025797&_gac=1.83792996.1606931023.Cj0KCQiAk53-BRD0ARIsAJuNhpt2i_LJ5AnSiYhTZjkGSp0e-_zMklgjMjQAabmzNgl-N8-nFVzWyjEaAvG2EALw_wcB "Google Maps Platform
ドキュメント")が充実していて、サンプルコードもたくさんあるので参考にしてみてください。