【RoadsAPI】GoogleMapに自分の移動経路を描写する方法

##今回のGPSロガーとは？
位置情報をブラウザで取得＆移動を監視し、
GoogleMap上にピンを立て、歩いた経路を表示するものです！
![main_image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558929/faa92507-0b80-7046-d7c7-b0ead6d127cf.png)

ピンをクリックするとinfoWindowと呼ばれるフキダシが表示されます。
![sub_image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558929/9156459d-3bd5-765b-f621-cd8ef1251239.png)

##使用技術
###HTML5/GeolocationAPI
[MDN] 
https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API

- 現在地の取得、位置情報の監視はHTML５のGeolocationAPIを使用します。

###GoogleMapsPlatform
[公式ドキュメント]
https://developers.google.com/maps/documentation/roads/intro?hl=ja
※日本語非対応

- マップを表示、ピンを立てるために**Maps JavaScript API**を使用します。
- 位置情報から通った経路を予測し、道に沿って線を描写するために**RoadsAPI**も合わせて使用します。

##GoogleMapsPlatformとは
GoogleMapsPlatformはその名の通り、
googleマップをwebサイトやアプリケーションに表示・カスタマイズするための様々なAPI等を提供しているサービスです。

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

料金については下の方に書き記して置きます。目次からどうぞ！
(気をつけないとGoogleマップ破産しちゃうよ！！！！)

個人的には、飲食店などの店舗のサイトにマップを埋め込んで、
現在地から店舗への経路をサイト上で表示できればと思ったのですが、破産必至ですね。


（ ´_ゝ`）ﾌｰﾝ


##geolocationAPIの解説
今回ミソとなるイカしたメソッドを解説します。

###位置情報の監視：watchPosition()
[MDN:Geolocation.watchPosition()](https://developer.mozilla.org/ja/docs/Web/API/Geolocation/watchPosition)
>Geolocation の watchPosition() メソッドは、機器の位置が変化するたびに自動的に呼び出されるハンドラー関数を登録するために用いられます。また必要に応じてエラー処理コールバック関数を指定することができます。

使い方は、

```js
navigator.geolocation.watchPosition(success[, error[, options]])
```

オプションには位置情報の精度や、タイムアウト時間や、位置情報の取得の間隔を指定することができます。

あくまでもオプションとされているので無くても問題は無いのですが、
取得間隔は指定してあげないと、かなりの高頻度で位置情報を取得し続けるので
バッテリーの消耗が早くなったりしてしまうので、指定することをオススメします。

また、ブラウザがgeolocationAPIに対応しているか、

```js
if (navigator.geolocation){...}
```

で囲うのがお作法のようです。

###位置情報の監視を止める:clearWatch()
また、watchPositionは止めない限りずっと位置情報を取得し続けるので、
止めてあげるために、watchPosition()メソッドが返すwatchIDを
clearWatch()メソッドに渡すようにします。

[MDN:Geolocation.clearWatch()](https://developer.mozilla.org/ja/docs/Web/API/Geolocation/clearWatch)

使い方は、以下の通り。

```js

watchId = navigator.geolocation.watchPosition(success[, error[, options]]);

navigator.geolocation.clearWatch(watchId);
```


##Maps JavaScript APIの用語解説
| 用語      |解説 |
|:-----------------|:------------------|
| marker         |     ピンのこと |
| polyline           |    指定した位置座標を結ぶ線のこと  |
| latitude           |           緯度 |
| longitude               |   経度 |

メソッドやプロパティは色々とあるので、
[これらのサイト](https://qiita.com/kotottt/items/a20329ff871153e4d501)を辞書的に使うとわかりやすいと思います。

APIの利用にはAPIキーの取得が必須なのでお忘れなく！

##コード
```html

<!DOCTYPE html>
<html>
<head>
  <title>GPSロガー</title>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
  <meta charset="utf-8">
  <link rel="stylesheet" type="text/css" href="css/style.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
  <div class="container">
    <div id="map"></div>
    <div class="btns">
      <div id="startButton" class="btn">Start</div>
      <div id="stopButton" class="btn">Stop</div>
    </div>
  </div>
</body>
<script>
  var map;
  var apiKey = '自分のAPIキー';
  var currentMarker = [];
  var currentWindow;
  var poly_storage = [];
  var position_options = { //位置情報取得時のオプション
    "enableHighAccuracy": false, //より精度の高い位置情報を取得するか
    "timeout": 60000, //タイムアウト
    "maximumAge": 10000, //取得の間隔(ミリ秒)
  };

  function initMap() {
    //マップセットアップ
    map = new google.maps.Map($('#map').get(0), {
      center: { lat: 35.6811673, lng: 139.7670516 },
      zoom: 6
    });

    // 位置情報を監視する
    function watchPosition() {
      if ("geolocation" in navigator) {
        watchId = navigator.geolocation.watchPosition(function (position) {
          // 緯度経度を取得する
          var lat = position.coords.latitude;
          var lng = position.coords.longitude;
          //ポリライン(経路)の描写
          addPolyLine(lat, lng)
        }, errorGetPosition, position_options)
      }
    }

    // 位置情報取得のエラー
    function errorGetPosition(error) {
      alert(error.code + ": " + error.message);
    }

    //位置情報の取得を止める
    function clearWatch(watchId) {
      navigator.geolocation.clearWatch(watchId);
    }

    //ポリライン(経路)の描写
    function addPolyLine(lat, lng) {
      var polyLatLng = [lat + ',' + lng];//注意ポイント[1]
      poly_storage.push(polyLatLng);
      snapPolyLine(poly_storage);
    }

    function snapPolyLine(poly_storage) {
      $.get('https://roads.googleapis.com/v1/snapToRoads', {
        interpolate: true,//道路に沿った地点間の補完
        key: apiKey,
        path: poly_storage.join('|')//注意ポイント[1]
      }, function (data) {
        processResponse(data)
      });
    }

    function processResponse(data) {
      var snappedCoordinates = [];
      for (var i = 0; i < data.snappedPoints.length; i++) {
        // スナップされた地点の取得
        var position = new google.maps.LatLng(
          data.snappedPoints[i].location.latitude,
          data.snappedPoints[i].location.longitude);
        snappedCoordinates.push(position);

        // スナップされた地点へのマーカー表示
        viewNewGooglemap(position);
      }

      // スナップされた地点をもとにPolylineを作成
      var polyline = new google.maps.Polyline({
        path: snappedCoordinates,
        strokeColor: "red",
        strokeWeight: 3
      });

      //Polylineをマップに表示
      polyline.setMap(map);
    }

    function getCurrentDateTime() {
      var current_date = new Date();
      var Y = current_date.getFullYear();
      var M = current_date.getMonth() + 1;
      var D = current_date.getDate();
      var h = current_date.getHours();
      var m = current_date.getMinutes();
      var s = current_date.getSeconds();
      var current_date = Y + "/" + M + "/" + D + "/" + h + ":" + m + ":" + s;

      return current_date;
    }
    
    //スナップされた地点へのマーカー表示
    function viewNewGooglemap(current_position) {
      //スナップされた地点に地図の中心と縮尺をセット
      map.setCenter(current_position);
      map.setZoom(20);

      //マーカーの設定
      var marker = new google.maps.Marker({
        position: current_position,
        map: map,
        animation: google.maps.Animation.BOUNCE
      });

      //新しいマーカーが立ったらマーカーのアニメーションを止める
      if (currentMarker.length > 0) {
        marker.setAnimation('null');
      }
      //マーカーの情報を保存しておく
      currentMarker.push(marker);

      //InfoWindowの設定
      var infoWindow = new google.maps.InfoWindow({
        content: '更新時間:' + getCurrentDateTime()
      });

      //マーカーをクリックしたらInfoWindowを開く
      marker.addListener('click', function () {
        //他のマーカーのInfoWindowが表示されていたら閉じる
        if (currentWindow) {
          currentWindow.close();
        }
        infoWindow.open(map, marker);
        //現在開いているInfoWindowとして保存しておく
        currentWindow = infoWindow;
        //クリックしたマーカーの位置に地図の中心をあわせる
        map.setCenter(current_position);
      });
    }

    $('#startButton').click(function () {
      watchPosition();
      alert('GPS追跡始めるで！')
    });

    $('#stopButton').click(function () {
      clearWatch(watchId);
      alert('止めたで！')
    });
  }
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=自分のAPIキー&callback=initMap"></script>
</html>
```

##注意するポイント
###[１]RoadsAPIにわたす緯度経度のフォーマット
RoadsAPIにわたす緯度経度(path)は、以下のように
緯度経度をカンマ区切りかつ、パイプ(|)つなぎになっていないといけません。
`path=-35.27801,149.12958|-35.28032,149.12907|-35.28099,149.12929`

そのため、取得した緯度経度をカンマ区切りにしストレージ用配列に追加しておきます。

```js
var polyLatLng = lat + ',' + lng;//注意ポイント[1]
poly_storage.push(polyLatLng);
```

その上で、RoadsAPIにわたす際に`join('|')`で繋いであげます。

```js
path: poly_storage.join('|')//注意ポイント[1]
```

##Maps JavaScript APIとRoadsAPIの料金について
GoogleMapsPlatformの料金全般については、[こちらのページ](
https://developers.google.com/maps/billing/gmp-billing?hl=ja&_ga=2.256916491.1614752047.1583758718-395971213.1575791191)にまとめられています。

毎月200ドル（約2万円分）が無料分として付与されるので、
個人利用なら破産の心配は無さそうですね。

今回使ったAPIの料金について少し解説してみます。

###Maps JavaScript API
[公式の料金ページ(dynamic-street-view)](https://developers.google.com/maps/billing/gmp-billing?hl=ja&_ga=2.256916491.1614752047.1583758718-395971213.1575791191#dynamic-street-view)いわく、
一回のリクエストごとに**$0.0056(¥0.58)**
`https://maps.googleapis.com/maps/api/js&key=自分のＡＰＩキー`
を読み込んでマップがロードされたタイミングでのみ料金が発生するみたいです。

ロードの定義を[公式Q&A](https://developers.google.com/maps/faq?hl=ja#usage-limits)より抜粋してみました。

> 
**How are map loads counted on the Google Maps Platform?**
A single map load is charged when any of the following occur:
A web page or application displays a map using the Maps JavaScript API.
An application requests a single map image from the Maps Static API.
Street View panoramas are charged separately from map loads:

途中省略
>After a web page or application loads a map, or a static map image, or a Street View panorama, any user interactions with it, such as panning, zooming, or switching map layers, do not generate additional map loads or affect usage limits.

マーカー立てやズームだけで1リクエストカウント→料金発生かと思いましたが、そうでは無いようです。

###RoadsAPI
[公式の料金ページ(Roads – Route Traveled)](https://developers.google.com/maps/billing/gmp-billing?hl=ja&_ga=2.256916491.1614752047.1583758718-395971213.1575791191#roads-route)いわく、
一回のリクエストごとに**$0.008(¥0.83)**

デモではwatchPosition()が走ったらその都度RoadsAPIをリクエストして道路補完をしています。
1分ごとに1回リクエストするとして、1時間で60回リクエスト
週2回1時間使うとすると、年間5,760回なので、
RoadsAPIだけで年間一人あたり¥4,780かかる計算。

(いい商売してるなあ....)

##まとめ
今回は簡易版をご紹介しましたが、もっと作り込めば、
緯度経度の変化が一定以上であればリクエストすることや、
移動距離も取得できるので、ぜひ個人利用の範囲にとどめてGoogleMapsPlatformを使ってみてはいかがでしょうか。。

間違い等あればご指摘下さい！！！
稚拙な解説ですが最後まで読んでいただき、ありがとうございました！