道を文字列に変換して保存する方法【Google Map JavaScript API, Ruby】

## 概要
本記事は RUNTEQアドベントカレンダー 2021 の10日目の記事となります！

自分はデリバリーのアルバイトをしているのですが、自分の使っている近道や裏道を保存して誰かと共有できたら良いなあと思いました。

そこで、現在勉強しているRuby,Ruby on Railsを使って道を保存してGoogle Mapに表示させるお試しの機能を作ってみました。

![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1232544/8d1ed7f6-6c5c-3285-c17e-29aeda5e5c78.gif)


## 記事を作成した理由


Qiitaの記事にもGoogle mapについての記事はたくさんありますが、道(一連の座標)を保存するやり方についての記事は無いような気がしたので、この記事を作成しました。

初学者ですので間違いがあるかもしれませんが、暖かい目で見て頂ければありがたいです。

## この記事で伝えたいこと
- Google Map JavaScript APIのざっくりとした使い方
- 道を1つの文字列として保存できるgemがあること（←ココを一番伝えたい！）

## 前提環境
MacOS: Catalina
ruby: 3.0.0
ruby on rails: 6.1.4

## 使用技術
- Google Maps Platform API(Map JavaScript API)
- Ruby gem for Google Maps APIs

## Google Maps Platform API(Map JavaScript API)

[Google Maps Platform API | Google Developers](https://developers.google.com/maps/apis-by-platform?hl=ja)
googleマップをwebサイトやスマホアプリに表示・カスタマイズするための様々なAPI等を提供しているサービスです。

料金が従量課金制なので、大量のアクセスが見込まれるサイトに使用する場合は注意が必要です。

今回はWebサービスで使いたいので[Map JavaScript API](https://developers.google.com/maps/documentation/javascript/examples)を使います。

これを使ってマップ上にマーカーとポリラインを作成します。ポリラインとは連続した線で表された線データのことです。


## Google Maps APIのためのRuby gem
[Ruby gem for Google Maps APIs](https://github.com/edwardsamuel/google-maps-services-ruby)

READMEに使い方が丁寧に書いてあり、とても親切だと思いました。
Google Maps APIの機能を持ったメソッドが使えるようになります。

今回使うのは、一連の座標を文字列として保存できるメソッドです。
[Polyline encoder/decoder](https://github.com/edwardsamuel/google-maps-services-ruby#polyline-encoderdecoder)

これが**めちゃ便利だな**と思いました。

道をどうやって保存すれば良いのかと思った時に、いくつかの座標を丸ごと保存するのかなー？
でもそうなるとカラムのデータ型は何なるのかな？json型?でも１つのカラムに複数のデータが入るのはよくないよなあ？
なんて思っていたのですが、この機能を使えば座標を文字列として保存できます。

#### ポリラインの座標を文字列に変換例
```ruby
path = [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
encoded_path = GoogleMapsService::Polyline.encode(path)
=> "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
```
#### 文字列をポリラインの座標に変換例
```ruby
encoded_path = '_p~iF~ps|U_ulLnnqC_mqNvxq`@'
path = GoogleMapsService::Polyline.decode(encoded_path)
=> [{:lat=>38.5, :lng=>-120.2}, {:lat=>40.7, :lng=>-120.95}, {:lat=>43.252, :lng=>-126.45300000000002}]
```
マジすげえ

機能自体はruby gemだけでなくnpmでも用意されているようです。
https://github.com/googlemaps/js-polyline-codec
## 実装した流れ
### gemのインストール
```ruby:
gem 'google_maps_service'
gem 'gon' # rubyの変数をjsに渡すために必要
```

### コード
#### ポリライン、マーカの作成

```html:new.html.erb
<input type="button" id="save" value="経路を保存する">
<div id="result"></div>
<div id="map"></div>
```

```js:new.js
// 必要なリンク
<script src="https://www.gstatic.com/external_hosted/jquery2.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=自分のAPIKey"></script>

<script>
var poly;
var map;
var save = document.getElementById("save");
var pathValues = [];

function initMap() {
  // マップを作成。中心はアメリカのシカゴ
  map = new google.maps.Map(document.getElementById("map"), {
    zoom: 7,
    center: { lat: 41.879, lng: -87.624 },
  });
  // ポリラインを引く
  poly = new google.maps.Polyline({
    strokeColor: "#000000",
    strokeOpacity: 0.5,
    strokeWeight: 10,
  });
  poly.setMap(map);
  map.addListener("click", addLatLng);
}

// 地図上でのクリックイベントを処理し、Polylineに新しいポイントを追加する
function addLatLng(event) {
  const path = poly.getPath();

  path.push(event.latLng);
  // ポリライン上に新しいマーカーを追加する
  new google.maps.Marker({
    position: event.latLng,
    map: map,
  });
}

  // 座標の配列をPostリクエストをAjaxで送信する
　save.addEventListener("click",function() {
    var path = poly.getPath();
    for (var i = 0; i < path.getLength(); i++) {
        pathValues.push(path.getAt(i));
    }
    $('#result').text('経路を保存しました！');
    $.ajax({
    url: '/maps',
    type: 'POST',
    dataType: 'json',
    data: {
        data: JSON.stringify(pathValues),
    },
    })
  });

$(window).load(initMap);
```
自分のAPIKeyを取得する方法を記載した公式リンクはこちら
https://cloud.google.com/docs/authentication/api-keys?hl=ja&visit_id=637746662897584655-1175073629&rd=1

元にした公式デモコードはこちら
https://developers.google.com/maps/documentation/javascript/examples/polyline-complex

#### postリクエストを受け取って保存する

一連の座標を文字列に変更し、mapsテーブルに保存しています。（テーブルの名前はpathsの方がわかりやすかったですね(^^;）

```ruby:maps#create
 def create
    pathValues_json_data =  params[:data]
    # jsonデータをrubyオブジェクトに変換
    pathValues = JSON.parse(pathValues_json_data)
    polylines = []
    latlng = []

    pathValues.each do |path|
      latlng = [path["lat"],path"lng"]]
      polylines.push(latlng)
    end
    # ポリラインの座標を文字列に変換
    encoded_path = GoogleMapsService::Polyline.encode(polylines)

    path = Map.new(encorded_path: encoded_path)
    path.save
  end
```

#### マイグレーションファイル(保存先のテーブル)
```ruby
class CreateMaps < ActiveRecord::Migration[6.1]
  def change
    create_table :maps do |t|
      # カラムに文字列型を指定
      t.string :encorded_path
    end
  end
end
```
#### データを取り出す
```ruby:maps#index
  def index
    paths = Map.all
    pathValues = []
    maps.each do |map|
    　# 文字列をポリラインの座標に変換
      path = GoogleMapsService::Polyline.decode(map.encorded_path)
      pathValues.push(path)
    end
    # rubyの変数をjsに渡すためにgonを使う
    gon.pathValues = pathValues
  end
```


```js:index.js
var poly;
var map;
var pathValues = gon.pathValues

function initMap() {
  map = new google.maps.Map(document.getElementById("map"), {
    zoom: 7,
    center: { lat: 41.879, lng: -87.624 }, // Center the map on Chicago, USA.
  });

  for (var i = 0; i < pathValues.length; i++) {
      var path = pathValues[i]
      drawPolyline(path);
  }
}


function drawPolyline(path) {
  poly = new google.maps.Polyline({
    path: path,
    strokeColor: "#000000",
    strokeOpacity: 0.5,
    strokeWeight: 10,
  });
  poly.setMap(map);
}
$(window).load(initMap);
```
## まとめ
改めてコードを見返すと、テーブルの名前はmapsではなかったですね(^^;
その他のコードももっとリファクタできたかもしれません。

今回の機能を作った収穫は座標を文字列で変換できる事を知ることができたことは良かったです。
スマホやGPS受信機を使って一連の座標を取得できればもっと良いなと思いました(^○^)

間違い等あればぜひご指摘いただければ幸いです。