Google交通情報の抽出ツールを作成しました（Googleマップ＋Googleスプレッドシート＋Google Distance Matrix API） 

# 目的
Google Apps Scriptと[Google Maps Platform](https://cloud.google.com/maps-platform/?authuser=1)の勉強のため、Googleスプレッドシートと[Google Distance Matrix API](https://developers.google.com/maps/documentation/distance-matrix/intro?authuser=1)を用いて、Google交通情報（旅行時間）を抽出ツールを作成してみました。

### [Distance Matrix API](https://developers.google.com/maps/documentation/distance-matrix/intro?authuser=1)
- 今回は、Google Maps Platformの中でも「ルート」に分類される「Distance Matrix API」を使用しています。
- Distance Matrix APIは、複数の目的地について移動時間と距離を計算します。

- Distance Matrix APIでは、移動モードを指定することができ（**今回は自動車**）、出発地・目的地の座標と出発予定時刻をHTTPリクエストすると旅行時間と距離をHTTPレスポンス（JSON形式）として受け取ることができます。
- 出発予定時刻は、現在の時刻または将来の時刻を設定することができ、過去の時刻を設定することはできません。
- 現在の時刻を設定すると限りなく現在に近い交通状況を考慮した旅行時間を取得することができます。一方で、未来の時刻を設定すると過去と現在の交通状況を考慮した旅行時間を取得することができます。

**※今回は、出発予定時刻に未来の時刻を設定した場合の旅行時間を抽出します。**

### Google Apps Script
- Google Apps Scriptは、Google社が提供するJavaScriptベースのプログラミング言語であり、クラウドサーバ上で動作します。
- Gmail、Googleスプレッドシート、Googleカレンダー、Googleドライブ、Google翻訳などのGoogleが提供する数々のアプリケーション群をプログラミングにより操作可能です。

# 実行環境
この記事ではGoogle Apps Scriptを使っています。
コードを実行するためにはGoogle Maps PlatformのAPIキーが必要です。

>※Google Maps Platform APIキーの取得・発行についてはこちらを参照してください。
https://www.zenrin-datacom.net/business/gmapsapi/api_key/index.html

# 前提条件
### 対象区間
旅行時間を抽出する区間は、[国土交通省の渋滞ランキングのとりまとめ（平成29年 年間・GW）](http://www.mlit.go.jp/report/press/road01_hh_000968.html)の**一般国道の年間渋滞1位の国道16号呼塚交差点～大井交差点（千葉県柏市）**を対象としました。

### 抽出条件
旅行時間の抽出条件は、以下のとおりとしました。
出発予定時刻：2019/03/18 06:00:00
データ取得間隔：15分
終了予定時刻：2019/03/18 19:00:00

# 実行方法

### 入力設定

- Googleスプレッドシートのlatlngシートにjunction,direction,lat,lngを入力してください。※必ず上り・下り別に入力してください。
- latとlngの取得方法は、[こちらの記事](https://qiita.com/wakusyan/items/f0666a7958b1ec6e86df)を参照ください。
- なお、latとlngは、Googleマップの検索ルート上の座標を正確に入力する必要があります（正確ではない場合、旅行時間の取得が上手くいかないようです）。
- 抽出結果を出力するresultシートを追加してください。

<img width="399" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/205045/5d7eb33f-79a7-3400-366c-43975c3d78e0.png">


### 実行
- Googleスプレッドシートのツール＞スクリプトエディタを開いて、下記のソースを貼り付け、基本的な設定を行ったあと、スクリプトを実行してください。

- Googleスプレッドシートへのアクセス権の確認画面が出ますので許可してください。

##### 基本的な設定
- APIキーを入力してください。
- 出発予定時刻（Unixtime）を入力してください。
- データ取得間隔（タイムインターバル）を入力してください。
- 終了予定時刻（Unixtime）を入力してください。

```javascript:gas1.gs
function CalcBetweenTwoPointsSpeed() {
  
  //基本的な設定
  //**********************************************************************************************
  // APIKEYを設定する
  var APIKEY = "APIキーを入力してください"
  
  // 出発予定時刻（Unixtime(UTC)）を設定する
  var departure_time_unix = 1552856400 // 2019/03/18 06:00:00
  
  // タイムインターバルを設定する
  var time_interval = 15 // 分単位
  
  // 終了予定時刻（Unixtime(UTC)）を設定する
  var finish_time_unix = 1552903200 // 2019/03/18 19:00:00
  //**********************************************************************************************
  
  // latlngシートの内容を配列に格納する
  var bk = SpreadsheetApp.getActiveSpreadsheet();             // アクティブなスプレッドシートを取得する
  var sh = bk.getSheetByName('latlng');                       // latlngシートを取得する
  var rowLst = sh.getLastRow();                               // 最終行を取得する
  var colLst = sh.getLastColumn();                            // 最終列を取得する
 
  /*
  Logger.log(rowLst);
  Logger.log(colLst);
  */
  
  // 方向別データ数のカウント
  var inCnt = 0 ;
  var outCnt = 0 ;
  for (var i = 2; i <= rowLst; i++) {
    if (sh.getRange(i, 2).getValues() == "inbound"){
      inCnt = inCnt + 1;
    }
    if (sh.getRange(i, 2).getValues() == "outbound"){
      outCnt = outCnt + 1;
    }
  }
  
  /*
  Logger.log(inCnt);
  Logger.log(outCnt);
  */
  
  // データを2次元配列に格納する
  var ArrInLst=sh.getRange(2,1,inCnt,colLst).getValues();
  var ArrOutLst=sh.getRange(2+inCnt,1,outCnt,colLst).getValues();
  
  /*
  Logger.log(ArrInLst);
  Logger.log(ArrOutLst);
  */
  
  // ループ処理
  var loopNumber = ( finish_time_unix - departure_time_unix ) / ( time_interval * 60 )
  // Logger.log(loopNumber);
  
  for (var k = 1; k <= loopNumber ; k++) {
    
    var d = new Date( departure_time_unix * 1000 );
    var year  = d.getFullYear();
    var month = d.getMonth() + 1;
    var day  = d.getDate();
    var hour = ( '0' + d.getHours() ).slice(-2);
    var min  = ( '0' + d.getMinutes() ).slice(-2);
    var sec   = ( '0' + d.getSeconds() ).slice(-2);
    var departure_time_jst = year + '/' + month + '/' + day + ' ' + hour + ':' + min + ':' + sec; // jst 
        
    //***********************
    //     inboundの処理
    //***********************
  
    var ArrInRes = [];
    
    // Distance Matrix APIの呼び出し
    for (var i = 0; i <= inCnt-2; i++) {
    
      // 緯度・経度を取得する
      var Lat1 = ArrInLst[i][2];   // 出発地緯度
      var Lon1 = ArrInLst[i][3];   // 出発地経度    
      var Lat2 = ArrInLst[i+1][2]; // 到着地緯度
      var Lon2 = ArrInLst[i+1][3]; // 到着地経度
    
      // HTTPリクエストを行う
      var url = "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric" +       
                "&departure_time="  + departure_time_unix + // 出発予定時刻(Unixtime(UTC))
                "&traffic_model=" + "best_guess" +          // 最適な旅行時間
                "&origins=" + Lat1 + "," + Lon1 +           // 出発地緯度経度
                "&destinations=" + Lat2 + "," + Lon2 +      // 到着地緯度経度
                "&key=" + APIKEY;                　　　　　　 // APIKEY  
      Logger.log(url);
    
      var res  = UrlFetchApp.fetch(url,{muteHttpExceptions:true}); // HTTPリクエストを行う
      Logger.log(res); 
    
      var json = JSON.parse(res.getContentText()); // HTTPレスポンスを文字列として取得、JSON形式の文字列を解析してオブジェクトとして返す
      Logger.log(json);
    
      if (json["status"] == "OK") {
        var str_dist = json["rows"][0]["elements"][0]["distance"]["value"];
        var str_dura = json["rows"][0]["elements"][0]["duration"]["value"];
        var str_dura_traff = json["rows"][0]["elements"][0]["duration_in_traffic"]["value"];      
        var dbl_spd = ( str_dist / 1000 ) / ( str_dura / 3600 ) 
        var dbl_spd_traff = ( str_dist / 1000 ) / ( str_dura_traff / 3600 )
      　
        ArrInRes[i] = [];
        ArrInRes[i][0] = departure_time_jst;
        ArrInRes[i][1] = str_dist;
        ArrInRes[i][2] = str_dura;
        ArrInRes[i][3] = dbl_spd;
        ArrInRes[i][4] = str_dura_traff;
        ArrInRes[i][5] = dbl_spd_traff;
      }
    }
  　
    // 計算結果をシートに出力する
  
    var sh = bk.getSheetByName('result'); // resultシートを取得する
    var rowLst_re = sh.getLastRow();      // 最終行を取得する
    // Logger.log(rowLst_re);
  
    for (var i = 0; i <= inCnt-2; i++) {
      // 値をセルに入力する
      sh.getRange(i+rowLst_re+1, 1).setValue(ArrInLst[i][0]);    // from
      sh.getRange(i+rowLst_re+1, 2).setValue(ArrInLst[i+1][0]);  // to
      sh.getRange(i+rowLst_re+1, 3).setValue(ArrInLst[i][1]);    // direction
      sh.getRange(i+rowLst_re+1, 4).setValue(ArrInLst[i][2]);    // y
      sh.getRange(i+rowLst_re+1, 5).setValue(ArrInLst[i][3]);    // x
      sh.getRange(i+rowLst_re+1, 6).setValue(ArrInLst[i+1][2]);  // y
      sh.getRange(i+rowLst_re+1, 7).setValue(ArrInLst[i+1][3]);  // x
      sh.getRange(i+rowLst_re+1, 8).setValue(ArrInRes[i][0]);    // time
      sh.getRange(i+rowLst_re+1, 9).setValue(ArrInRes[i][1]);    // distance
      sh.getRange(i+rowLst_re+1, 10).setValue(ArrInRes[i][2]);   // duration
      sh.getRange(i+rowLst_re+1, 11).setValue(ArrInRes[i][3]);   // speed
      sh.getRange(i+rowLst_re+1, 12).setValue(ArrInRes[i][4]);   // duration_in_traffic
      sh.getRange(i+rowLst_re+1, 13).setValue(ArrInRes[i][5]);   // speed
    }
    
    //***********************
    //     outboundの処理
    //***********************
  
    var ArrOutRes = [];
    
    // Distance Matrix APIの呼び出し
    for (var i = 0; i <= inCnt-2; i++) {
    
      // 緯度・経度を取得する
      var Lat1 = ArrOutLst[i][2];   // 出発地緯度
      var Lon1 = ArrOutLst[i][3];   // 出発地経度    
      var Lat2 = ArrOutLst[i+1][2]; // 到着地緯度
      var Lon2 = ArrOutLst[i+1][3]; // 到着地経度
    
      // HTTPリクエストを行う
      var url = "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric" +       
                "&departure_time="  + departure_time_unix + // 出発予定時刻(Unixtime(UTC))
                "&traffic_model=" + "best_guess" +          // 最適な旅行時間
                "&origins=" + Lat1 + "," + Lon1 +           // 出発地緯度経度
                "&destinations=" + Lat2 + "," + Lon2 +      // 到着地緯度経度
                "&key=" + APIKEY;                　　　　　　 // APIKEY  
      Logger.log(url);
    
      var res  = UrlFetchApp.fetch(url,{muteHttpExceptions:true}); // HTTPリクエストを行う
      Logger.log(res); 
    
      var json = JSON.parse(res.getContentText()); // HTTPレスポンスを文字列として取得、JSON形式の文字列を解析してオブジェクトとして返す
      Logger.log(json);
      
      if (json["status"] == "OK") {
        var str_dist = json["rows"][0]["elements"][0]["distance"]["value"];
        var str_dura = json["rows"][0]["elements"][0]["duration"]["value"];
        var str_dura_traff = json["rows"][0]["elements"][0]["duration_in_traffic"]["value"];      
        var dbl_spd = ( str_dist / 1000 ) / ( str_dura / 3600 ) 
        var dbl_spd_traff = ( str_dist / 1000 ) / ( str_dura_traff / 3600 )
      　
        ArrOutRes[i] = [];
        ArrOutRes[i][0] = departure_time_jst;
        ArrOutRes[i][1] = str_dist;
        ArrOutRes[i][2] = str_dura;
        ArrOutRes[i][3] = dbl_spd;
        ArrOutRes[i][4] = str_dura_traff;
        ArrOutRes[i][5] = dbl_spd_traff;    
      }
    }
  
    // 計算結果をシートに出力する
  
    var sh = bk.getSheetByName('result'); // resultシートを取得する
    var rowLst_re = sh.getLastRow();      // 最終行を取得する
    // Logger.log(rowLst_re);
  
    for (var i = 0; i <= outCnt-2; i++) {
      // 値をセルに入力する
      sh.getRange(i+rowLst_re+1, 1).setValue(ArrOutLst[i][0]);    // from
      sh.getRange(i+rowLst_re+1, 2).setValue(ArrOutLst[i+1][0]);  // to
      sh.getRange(i+rowLst_re+1, 3).setValue(ArrOutLst[i][1]);    // direction
      sh.getRange(i+rowLst_re+1, 4).setValue(ArrOutLst[i][2]);    // y
      sh.getRange(i+rowLst_re+1, 5).setValue(ArrOutLst[i][3]);    // x
      sh.getRange(i+rowLst_re+1, 6).setValue(ArrOutLst[i+1][2]);  // y
      sh.getRange(i+rowLst_re+1, 7).setValue(ArrOutLst[i+1][3]);  // x
      sh.getRange(i+rowLst_re+1, 8).setValue(ArrOutRes[i][0]);    // time
      sh.getRange(i+rowLst_re+1, 9).setValue(ArrOutRes[i][1]);    // distance
      sh.getRange(i+rowLst_re+1, 10).setValue(ArrOutRes[i][2]);   // duration
      sh.getRange(i+rowLst_re+1, 11).setValue(ArrOutRes[i][3]);   // speed
      sh.getRange(i+rowLst_re+1, 12).setValue(ArrOutRes[i][4]);   // duration_in_traffic
      sh.getRange(i+rowLst_re+1, 13).setValue(ArrOutRes[i][5]);   // speed
    }
    departure_time_unix = departure_time_unix + time_interval * 60
  }
}
```

# 実行結果
- 旅行時間は、L列のduration_in_traffic(s)になります。
- 旅行速度は、M列のspeed_in_traffic(km/h)になります。
- ※旅行速度は、I列のdistance(m)とL列のduration_in_traffic(s)から求めています。
<img width="864" alt="キャプチャ2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/205045/8b33f290-3ebb-d773-c65a-96f2f0aad1d4.png">

- GoogleスプレッドシートをExcel形式でダウンロードし、旅行速度を可視化すると以下のようになります。
![プレゼンテーション1.png](https://qiita-image-store.s3.amazonaws.com/0/205045/ce03d037-dc48-9a89-26df-ca42dc94c445.png)


※背景地図はOpenStreetMapを使用しています。

# 留意事項
- Google Maps PlatformのAPIを利用するには、APIキーが必須であり、無料枠内（1ヵ月 200ドルの無料クレジット）で利用するのにも請求先アカウント（クレジットカード）の設定が必要です。
- Distance Matrix APIは、交通状況を考慮しないDistance Matrixと交通状況を考慮するDistance Matrix Advancedに大別されます。
- 今回使用したDistance Matrix APIは、Distance Matrix Advancedになります。
- Distance Matrix Advancedの価格設定は以下のとおりです。
  - 0-100,000ELEMENT/月(10.00米ドル/1,000ELEMENT)
  - 100,001-500,000ELEMENT/月(8.00米ドル/1,000ELEMENT)
  - 500,000-ELEMENT/月(問い合わせ）
  - ※ELEMENTは、APIで送信されるoriginsとdestinationsの要素数のことです。
- [したがって、無料枠（200米ドル）を最大限利用した場合、1ヶ月あたりに利用できるのは、20,000ELEMENT（リクエスト）となります。](https://cloud.google.com/maps-platform/pricing/sheet/?authuser=1)

**※無料枠を超えてAPIを使用すると課金されますのでご注意ください(>_<)**

# 今後の課題
- [エンコードされたポリラインアルゴリズム形式への変更](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)

# 参考書
[詳解! GoogleAppsScript完全入門 ~GoogleApps & G Suiteの最新プログラミングガイド~](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3-GoogleAppsScript%E5%AE%8C%E5%85%A8%E5%85%A5%E9%96%80-GoogleApps-G-Suite%E3%81%AE%E6%9C%80%E6%96%B0%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%AC%E3%82%A4%E3%83%89/dp/4798053767)
[Developer Guide（Distance Matrix API）](https://developers.google.com/maps/documentation/distance-matrix/intro?authuser=1)

# 次回の投稿予定
- 出発予定時刻を現在の時刻にした場合の旅行時間の抽出ツール
 - 基本的にはDistance Matrix APIを用いますが、現在時刻の設定にはGoogle Apps Scriptのトリガー機能を用います。

または

 - [Geocoding APIを用いた住所の地理的座標への変換ツール](https://qiita.com/wakusyan/items/f6fef91a18f3d0a22da2)

になる予定です。