Google Maps Routes API（プレビュー版）を使って複数地点を経由した距離を取得してみる

# はじめに
Google Mapsの新しくRoutes APIが提供されたので、ComputeRoutesを使って複数の中間地点を経由した場合の移動距離を取得していきます。

# 背景と目的
## 背景
弊社ではガスや灯油の個人宅への配送を支援するサービスを提供しています。
最近、配送に関する業種ではカーボンニュートラルが話題となっており、配送する際の移動距離が重要になっていると聞き及んでいます。
こちらの移動距離の参照のため、弊社サービスで作成した配送計画での移動距離を算出する機能の実装を検討しています。

## 目的
弊社サービスで出力した配送計画の順番と位置情報をもとに、移動距離と移動時間を算出します。
それらを算出するのにGoogle Maps Routes APIが利用できるかの確認を行っていきます。

### やること
+ Google Maps Routes APIのComputeRoutesの仕様を確認
+ 弊社サービスでの出力結果からComputeRoutesへのつなぎ方を検討

## 事前準備（おさらい含む）
### Google Maps Routes APIのComputeRoutesのドキュメント
流石にプレビュー版なので本家のドキュメント、移行ガイドを参考にします。
https://developers.google.com/maps/documentation/routes/reference/rest/v2/TopLevel/computeRoutes
https://developers.google.com/maps/documentation/routes/compute_route_directions

## やってみた
今回はいきなりコーディングはせずに、cURLを用いて動作確認を行っていきます。
実際にAPIを使う場合にはGCPコンソールから、Route APIの有効化と必要に応じて認証系の許可も開けておきます。

### API仕様の確認
Routes APIを使う上でデータを渡すためのPayload（JSON）と必要なフィールドをフィルターするリクエストヘッダ情報に分かれています。

#### リクエストヘッダ（X-Goog-FieldMask)
サンプルのcURLコマンドを見ると、duration（時間）、distanceMeters（移動距離）、encodedPolyline（地図上での経路表記）が含まれていますが、今回取得するのは時間と移動距離なのでencodedPolylineは削ります。
他にも利用できるフィールドはありますが、必要なものだけに絞った方が早くなるそうなので、必要になったら追記とします。

```:サンプルのヘッダ
'X-Goog-FieldMask: routes.duration,routes.distanceMeters,routes.polyline.encodedPolyline'
```

```:変更後のヘッダ
'X-Goog-FieldMask: routes.duration,routes.distanceMeters'
```

#### ペイロード（JSON)の設定
ペイロードのJSONデータを設定していきます。
サンプルでは、origin、destination、そして各種パラメータとなっています。
今回は複数地点での距離データなので、それに加えて経由地点であるintermediatesも設定します。
動作確認では出発を弊社オフィス、経由は東京駅、到着は弊社技術センターを設定します。

originとdestinationは緯度経度のみのシンプルな構造となっています。
経由地点はarrayになっていますが、こちらも基本はorigin/destinationと同じ記法となります。
経由地点では荷降ろしを想定しているため、トンネルや高速道路など降車に適さない道路と一般道路が重なった場所に、
降車ができる一般道路を選択できるよう、vehicleStopoverの値をtureとします。

```:ペイロード（地点部分）
    "origin":{
        "location":{
            "latLng":{
                "latitude": 35.685602,
                "longitude": 139.7364423
            }
        }
    },
    "destination":{
        "location":{
            "latLng":{
                "latitude": 35.6484628,
                "longitude": 139.7985855
            }
        }
    },
    "intermediates":[
        {
            "vehicleStopover": true,
            "location":{
                "latLng":{
                    "latitude": 35.6823502,
                    "longitude": 139.7636297
                }
            }
        }
    ],
```

続いて、経路を作成するための各種パラメータを設定していきます。
+ travelMode: 経路設計時の移動手段の設定です。配送計画はトラックを想定しているため、DRIVEを設定します。
+ routingPreference: 経路設計時に交通状況等を考慮するかの設定です。今回は交通状況に左右されないTRAFFIC_UNAWAREを設定します。
理想では交通状況を加味した計算ができるといいのですが、実行タイミングによって結果が変わると評価材料に適さないため、今回はこの設定となります。
+ computeAlternativeRoutes: 代替ルートの設定です。今回は不要なのでfalseとします。
+ routeModifiers: 有料道路利用等の経路設定の条件です。avoidTollsとavoidHighwaysは有料道路と高速道路の利用可否設定、avoidFerriesはフェリー利用可否設定です。今回はいずれも利用しないためtrueとします。
+ languageCode: 表示言語の設定です。今回の結果にはあまり意味がないですが、なんとなく日本語設定しています。

```:ペイロード（各種パラメータ）
    "travelMode": "DRIVE",
    "routingPreference": "TRAFFIC_UNAWARE",
    "computeAlternativeRoutes": false,
    "routeModifiers": {
        "avoidTolls": true,
        "avoidHighways": true,
        "avoidFerries": true
    },
    "languageCode": "ja-JP"
```

#### 実行してみよう
実際にこの設定で実行すると、東京駅を経由した移動距離と時間が出てきます。
WebサービスのGoogle Mapsで経路検索した結果と大きく差は無いため、値は取れているようです。

```ShellScript:getDistance.sh
#!/bin/bash
curl -X POST -d '{
    "origin":{
        "location":{
            "latLng":{
                "latitude": 35.685602,
                "longitude": 139.7364423
            }
        }
    },
    "destination":{
        "location":{
            "latLng":{
                "latitude": 35.6484628,
                "longitude": 139.7985855
            }
        }
    },
    "intermediates":[
        {
            "vehicleStopover": true,
            "location":{
                "latLng":{
                    "latitude": 35.6823502,
                    "longitude": 139.7636297
                }
            }
        }
    ],
    "travelMode": "DRIVE",
    "routingPreference": "TRAFFIC_UNAWARE",
    "computeAlternativeRoutes": false,
    "routeModifiers": {
        "avoidTolls": true,
        "avoidHighways": true,
        "avoidFerries": true
    },
    "languageCode": "ja-JP"
}' -H 'Content-Type: application/json' -H 'X-Goog-Api-Key: <your api key> -H 'X-Goog-FieldMask: routes.duration,routes.distanceMeters' 'https://routes.googleapis.com/directions/v2:computeRoutes'
```

実行結果は以下の通り、代替ルートは無しとしているので結果は1つ、項目のフィルターをしているので距離（distanceMeters）と時間（duration）のみ買ってきます。
距離はメートル単位なので、利用する場合にはいい感じにkmへ換算しましょう。そうしましょう。
```:実行結果
{
  "routes": [
    {
      "distanceMeters": 9705,
      "duration": "1639s"
    }
  ]
}
```

## 今後の展望
### 配送計画への適用案
今回の動作確認で、ペイロード部分に緯度経度のデータを渡すことで複数地点を経由した距離と移動時間がとれることがわかりました。
また、computeRoutesではintermediatesの上限数が25箇所までとなるため、この上限を加味した実装が必要となります。
実際に弊社サービスとの連携をシンプルに考えると以下の以下の通りの実装案になります。

+ 配車計画から緯度経度のリストを取得
+ 緯度経度のリストの長さが27を超えている場合、27以下の複数のリストに分割
+ 各リストの先頭をorigin、末尾をdestination、それ以外はintermediatesへ設定し、APIを実行
+ 実行結果のdistanceMetersを加算し、その結果を総移動距離として取得

幸い弊社サービスでは計画作成、シミュレーション結果ともに緯度経度情報があるので、上記の処理を共通化し入力を変えることで実現できそうです。

# まとめ
Google Maps Routes APIのcomputeRoutesを使って、複数地点の移動距離を取得、及び、既存の処理との連携方法の検討を行いました。
こちらのAPIを利用することで、カーボンニュートラルの指標の一つである移動距離の算出ができることを確認しました。
今後の作業は既存の処理との連携となります。処理自体は複雑なものにはならないと想定してますが、単純に距離表示だけでなくレポート内容として有効な手段を検討していきたいと考えています。