【Rails】Google mapのマーカー(ピン)をカスタマイズして遊んでみた

## はじめに
こんにちは。[kosukein38](https://twitter.com/kosukein38)です。
最近仕事でGoogle map API(`Maps JavaScript API`)を使っていました。
後学も兼ねて、マーカー(ピン📍)を色々カスタマイズして遊んでみました🥳
自分は"ピン"を立てるとかってよくいうのですが、公式に習って、本記事では"マーカー"と呼ぶことにします。
徐々にステップアップする感じで書いてみたので、良かったら参考にしていただけたら幸いです。

## この記事の対象者

- Google map API(`Maps JavaScript API`)を初めて触る方
- RailsアプリにGoogle mapを組み込んで、あれこれ情報を表示したい方


## 内容（この記事でやること、やらないこと）

### やること
- Google map API(`Maps JavaScript API`)をRailsに導入する
- 地図を描画する
- 地図にマーカーを立てる
- マーカーの見た目を変える
- マーカーに吹き出しをつける + マーカークリックで吹き出しを表示する
- 【ちょっと応用】複数のマーカーを設置、その属性値で吹き出しやHTMLを書き換える

### やらないこと
- Rails側の設定やAPIの設定の詳細
    - 詳しい設定方法は書いていません、Googleの公式ドキュメントや、またasset周り設定方法は公式ドキュメント、Railsガイドを参照してください。

## 本題
### Google map API(`Maps JavaScript API`)をRailsに導入する
まずは以下の公式ドキュメントを参照ください。
以下の公式ドキュメントを参考にしてください

[Maps JavaScript API を読み込む  |  Google for Developers](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#use-legacy-tag)

> 以下の 3 つの方法で読み込むことができます。
> 
> - [Dynamic Library Import](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#dynamic-library-import) を使用する（推奨）
> - [NPM js-api-loader パッケージ](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#js-api-loader)を使用する
> - [以前のスクリプト読み込みタグ](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#use-legacy-tag)を使用する

ちょっと古い方法のようですが、今回は3つめのスクリプト読み込みタグを利用します。

以下のタグをGoogle Mapを読み込ませたいビューに配置します。
```html
<script async
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
```
APIキーは秘匿情報に当たるので、`.env`や`Credentials`で管理してください。
自分はRails4.2を使って（古いのは承知の上ですw）テストしましたので、以下のようなビューを作りました。

```erb:app/views/test.html.erb
<div class="mt10" id='custom-map'></div>
<div id='text'>
  <h1>テスト文字列</h1>
</div>

<%= javascript_include_tag 'test' %>
<%= javascript_include_tag "https://maps.googleapis.com/maps/api/js?key=#{ENV['API_KEY']}&libraries=places&callback=initMap" %>

```

ビューを表示できるように、適当にコントローラーとルーティングを設定します。（詳細は省略）
今回は`test.js`ファイルにJavaScriptを書いて、操作していきます。

### 地図を描画する

[Maps JavaScript API を読み込む  |  Google for Developers](https://developers.google.com/maps/documentation/javascript/load-maps-js-api?hl=ja#use-legacy-tag)

initMap()関数でGoogleMapを描画します。

```javascript:app/assets/javascripts/test.js
let map;

function initMap() {
  map = new google.maps.Map(document.getElementById("custom-map"), {
    center: { lat: 35.658581, lng: 139.745433 },
    zoom: 12,
  });
}

window.initMap = initMap;


```
結果はこんな感じです。簡単に地図が表示できますね。(For development purposes onlyは気にせず...w)

![スクリーンショット 2023-11-23 23.42.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1697316/c6f4ac55-56c0-32bc-4794-b246827c9cb0.png)


### 地図にマーカーを立てる

[マーカー  |  Maps JavaScript API  |  Google for Developers](https://developers.google.com/maps/documentation/javascript/markers?hl=ja)

実際にマーカーを立てていきます`marker = new google.maps.Marker()`でマーカーのインスタンスを作成します。

```javascript:app/assets/javascripts/test.js
let map;

function initMap() {
  const position = { lat: 35.658581, lng: 139.745433 };
  map = new google.maps.Map(document.getElementById("custom-map"), {
    center: { lat: 35.658581, lng: 139.745433 },
    zoom: 8,
  });

  marker = new google.maps.Marker({
    position: position,
    map: map,
    title: "tokyo-tower",
  });
}

window.initMap = initMap;
```
結果はこんな感じ、見慣れたマーカー📍が立ちました。

![スクリーンショット 2023-11-23 23.45.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1697316/b4e2f1bc-8372-d561-9b1f-c23d4ab32a63.png)

### マーカーの見た目を変える

[Google マップのカスタマイズ: カスタムのマーカー  |  Maps JavaScript API  |  Google for Developers](https://developers.google.com/maps/documentation/javascript/custom-markers?hl=ja)

マーカーがデフォルトのままでは寂しいのでちょっと変えてみましょう。
例えばRailsでは`public/img/map_logo.png`といったように、マーカーにしたい画像ファイルを配置してみます。
↓こんな画像
![map_logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1697316/20391790-c588-ab01-a251-68fa037a25f9.png)


```javascript:app/assets/javascripts/test.js
let map;

function initMap() {
  const position = { lat: 35.658581, lng: 139.745433 };
  map = new google.maps.Map(document.getElementById("custom-map"), {
    center: { lat: 35.658581, lng: 139.745433 },
    zoom: 8,
  });
  
  marker = new google.maps.Marker({
    position: position,
    map: map,
    title: "tokyo-tower",
  });

  marker.setOptions({
    icon: { 
      url: '/img/map_logo.png'
    }
  });
}

window.initMap = initMap;
```

結果はこんな感じ、マーカーに色がつきました！

![スクリーンショット 2023-11-23 23.50.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1697316/d1eee32a-565f-0625-81aa-680e7540ff2b.png)

### マーカーに吹き出しをつける + マーカークリックで吹き出しを表示する

[情報ウィンドウ  |  Maps JavaScript API  |  Google for Developers](https://developers.google.com/maps/documentation/javascript/infowindows?hl=ja)

さらにレベルアップして、マーカーをクリックすると、情報ウィンドウ（吹き出し）が出てくるようにしてみましょう。

```javascript:app/assets/javascripts/test.js
let map;

function initMap() {
  const position = { lat: 35.658581, lng: 139.745433 };
  const contentString =
  '<div class="ks_pin">'
  + '<div class="mb5"><a href="'
  + 'https://ja.wikipedia.org/wiki/%E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC'
  + '" class="btn btn-sm btn-default mb5"'
  + '>'
  + 'wikipediaを見る'
  + '</a></div>'
  + '</div>';

  const infowindow = new google.maps.InfoWindow({
    content: contentString,
  });

  map = new google.maps.Map(document.getElementById("custom-map"), {
    center: { lat: 35.658581, lng: 139.745433 },
    zoom: 12,
  });

  marker = new google.maps.Marker({
    position: position,
    map: map,
    title: "tokyo-tower",
  });

  marker.setOptions({
    icon: { 
      url: '/img/map_logo.png'
    }
  });

  marker.addListener("click", () => {
    infowindow.open({
      anchor: marker,
      map,
    });
  });
}

window.initMap = initMap;
```
結果はこんな感じ、簡単に吹き出しを出すことができます。

![9d6496d5870a7917a66096f7ecddc9f3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1697316/01b744cc-9639-68f2-b026-1c669f736213.gif)


### 【ちょっと応用】複数のマーカーを設置、その属性値で吹き出しやHTMLを書き換える
最後にちょっと応用編ということで、2つのマーカーを設置して、その属性値を利用して吹き出しの内容や、ビューのHTMLを書き換えるようなことをやってみます。

```javascript:app/assets/javascripts/test.js
let map;
let marker = [];
let infoWindow = [];
let previous_i;

// マーカーにクリックイベントを付与
function markerEvent(i) {
  marker[i].addListener('click', function() {
    let markerTitle = marker[i].title;
    toggleText(markerTitle);
    // 前のピンをクリックしたら吹き出しをクローズする処理
    if (previous_i !== undefined) {
      infoWindow[previous_i].close(map, marker[previous_i]);
    }
    infoWindow[i].open({
      anchor: marker[i],
      map,
    });
    previous_i = i
  });
}

// マーカーに付与する吹き出しの詳細
function windowDetail(obj) {
  if (obj.title === "tokyo-tower") {
    return '<div>'
    + '<div class="mb5"><a href="'
    + 'https://ja.wikipedia.org/wiki/%E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC'
    + '" class="btn btn-sm btn-default mb5"'
    + '>'
    + 'wikipediaを見る'
    + '</a></div>'
    + '</div>';
  } else if (obj.title === "sky-tree") {
    return '<div>'
    + '<div class="mb5"><a href="'
    + 'https://ja.wikipedia.org/wiki/%E6%9D%B1%E4%BA%AC%E3%82%B9%E3%82%AB%E3%82%A4%E3%83%84%E3%83%AA%E3%83%BC'
    + '" class="btn btn-sm btn-default mb5"'
    + '>'
    + 'wikipediaを見る'
    + '</a></div>'
    + '</div>';
  }
}

// マーカーのタイトル属性でテキストを書き換える
function toggleText(title) {
  let h1Element = document.querySelector('#text h1');
  if (title === "tokyo-tower") {
    h1Element.innerHTML = "東京タワーですよ！";
  } else if (title === "sky-tree") {
    h1Element.innerHTML = "スカイツリーですよ！";
  }
}

// 複数のデータを受け取ってマーカーを設置する
function setMarkers(data) {
  for (var i = 0; i < data.length; i++) {
    // マーカーを立てる
    position = {lat: parseFloat(data[i]['lat']), lng: parseFloat(data[i]['lng'])};
    markerLatLng = new google.maps.LatLng(position);
    marker[i] = new google.maps.Marker({
      position: markerLatLng,
      map: map,
      title: data[i]['title'],
    });
    // マーカーのアイコンを変える
    marker[i].setOptions({
      icon: { 
        url: '/img/map_logo.png'
      }
    });
    // マーカーに吹き出しセット
    infoWindow[i] = new google.maps.InfoWindow({
      content: windowDetail(data[i])
    });
    // マーカーにクリックイベントセット
    markerEvent(i);
  }
}

// 地図の描画処理
function initMap() {
  data =[
    {
      title: "tokyo-tower",
      lat: "35.65871123089065",
      lng: "139.74537925206033",
    },
    {
      title: "sky-tree",
      lat: "35.71292005728178",
      lng: "139.81072185646673",
    },
  ]

  map = new google.maps.Map(document.getElementById("custom-map"), {
    center: { lat: 35.658581, lng: 139.745433 },
    zoom: 12,
  });

  setMarkers(data);
}

// initMap関数の呼び出し
window.initMap = initMap;

```
結果はこんな感じ。あなたは東京タワー派ですか?スカイツリー派ですか??

![a1b998f83557b3537b6df1ebd95f88d8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1697316/29698b72-d83f-7cc4-c803-9d658ba18dc4.gif)


## まとめ

- 最後の例を応用すると、例えば、RailsバックエンドのAPI経由でDBのデータをJSONで取得して、そのデータ配列からピンを生成するみたいなこともできますね。
- GoogleMap APIは色んなプロダクトでよく使われるAPIだと思いますので、参考にしていただけたら幸いです。
- 本記事で紹介したコードで、非推奨のメソッドなどが今後出てくる可能性もありますので、最新情報は必ず公式ドキュメントで確認してください。
- 最後まで読んでいただきありがとうございました。

## 参考

https://developers.google.com/maps/documentation/javascript?hl=ja