[GAS] ２地点間のルートを描画したマップ画像をスプレッドシートに挿入するにはStaticMapとDirectionFinderを使う

## はじめに
案件内でGoogleMapの埋め込みではなく、画像化したものをスプレッドシート上に挿入する、という工程がありました。`Maps.StaticMap`を使えばよいのですが、その際、任意の２地点間のルートも描画する必要があったため、ルート検索には`Maps.DirectionFinder`を使いました。本記事はその備忘録です。

## 実行サンプル
出発地と目的地を入力し、スクリプトを実行すると、枠外配置でマップ画像が挿入されます。
![staticmap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1111613/1ea08bf2-2545-5b6f-4b92-1e781ef6009c.png)

上記は出発地に`東京駅`、目的地に`スカイツリー`を指定しています。サンプルのため、ルート検索の移動手段に関しては公共交通利用の`TRANSIT`固定とし[^1]、複数候補のうちの１つ目のみを描画しています。

[^1]: 指定できる移動手段は[公式リファレンス](https://developers.google.com/apps-script/reference/maps/mode)を確認してください。セレクトメニューなどで選択できるようにしても良いと思います。

## ロジック
### 1-Maps.DirectionFinderで２地点間のルートを取得
```Javascript: Sample01.gs
const directions = Maps.newDirectionFinder()
      .setLanguage('ja')
      .setOrigin(start)
      .setDestination(end)
      .setMode(Maps.DirectionFinder.Mode.TRANSIT) // 公共交通
      .setDepart(new Date())
      .getDirections()

const route = directions.routes[0].overview_polyline.points // エンコードされたルート情報取得
```

コード中`start`と`end`には事前に取得してある出発地と目的地の値が入ります（今回は東京駅とスカイツリー）。

`directions`はJSON形式で、今回必要なデータが`routes`という配列で取得[^2] できます。候補が複数あっても最初の候補のみを取得するようにしています。この内、`overview_polyline.points`というのが、出発地から目的地までのルート描画に必要な経由ポイントの緯度経度情報を文字列化したもの[^3] となります。

[^2]: 取得できるJSON形式の構造サンプルは[公式ドキュメント](https://developers.google.com/maps/documentation/directions/start)にて確認できます。

[^3]: [緯度,経度, ... ,緯度,経度]といった具合に緯度と経度を順番に列挙した１次元配列をエンコードしたものとなります。アルゴリズムは[こちら](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)で確認できます。

### 2-Mapの設定
```Javascript: Sample02.gs
const map = new Maps.newStaticMap()
/* 省略 */

map.setSize(800, 450)
  .setLanguage('ja')
  .setPathStyle(4, Maps.StaticMap.Color.RED, null)
  .addPath(route)
```

`Maps.StaticMap`を利用します。

`setPathStyle(4, Maps.StaticMap.Color.RED, null)`でルートの描画設定をします。今回は赤で描画します。今回のようなエンコードされたデータ[^3] がある場合、`addPath(route)`でルートを描画します。適切な形式の緯度経度情報配列[^4] でも描画されます。
[^4]: [^3]のエンコード前の１次元配列となります。

単純に２地点間を直線で描画する場合には以下の様なコードになります。

```Javascript: Sample03.gs
map.setSize(800, 450)
  /* 省略 */
  .beginPath()
  .addPoint(start)
  .addPoint(end)
  .endPath()
```

### 3 -PINGとして指定セル上に挿入
```Javascript
const Sh  = SpreadsheetApp.getActiveSheet()
const Rng = Sh.getRange('B2:C2')
/* 省略 */

const png = Utilities.newBlob(map.getMapImage(), 'image/png', 'map.png')
const pos = Rng.offset(2,0,1,1)
Sh.insertImage(png, pos.getColumn(), pos.getRow())
```

`Utilities.newBlob()`でPING画像に変換し、`insertImage()`で指定したセルの左上を起点として挿入します。

余談ですが、`insertImage()`は`Range`ではなく`Sheet`のメソッドですね。感覚的には前者なので、リファレンスを見て慌てることがあります。

なお、変換した画像のリンクURLを取得するには、Maps Static APIを利用するためのAPIキーが必要になります。

## 今回のコード
```Javascript: insertStaticMap.gs
function insertStaticMap(){
  const map = new Maps.newStaticMap()
  const Sh  = SpreadsheetApp.getActiveSheet()
  const Rng = Sh.getRange('B2:C2') // 出発地と目的地の取得
  const [start, end] = [...Rng.getValues()[0]]
  
  // ２地点間ルート取得
  const directions = Maps
      .newDirectionFinder()
      .setLanguage('ja')
      .setOrigin(start)
      .setDestination(end)
      .setMode(Maps.DirectionFinder.Mode.TRANSIT) // 公共交通
      .setDepart(new Date())
      .getDirections()

  const route = directions.routes[0].overview_polyline.points // エンコードされたルート情報取得

  // Mapの設定
  map.setSize(800, 450)
  .setLanguage('ja')
  .setPathStyle(4, Maps.StaticMap.Color.RED, null)
  .addPath(route)

  // PINGとして指定セル上に挿入
  const png = Utilities.newBlob(map.getMapImage(), 'image/png', 'map.png')
  const pos = Rng.offset(2,0,1,1)
  Sh.insertImage(png, pos.getColumn(), pos.getRow())
}
```

入力された施設名や住所に対してエラー判定は行っていませんので、参考にする場合は必要に応じて処理を実装してください。また、続けて処理を実行する場合、新たに挿入した画像は重ねて表示されますので、以下のような事前処理が必要です。

```Javascript
const img = Sh.getImages()[0]
if(img)img.remove()
```

## 最後に
ルート描画の必要がなければ`Maps.DirectionFinder`を利用した処理は不要です。その他マーカースタイルやズーム、中心設定など`Maps.StaticMap`でカスタマイズできる内容は[公式リファレンス](https://developers.google.com/apps-script/reference/maps/static-map)を参照してください。

### [参考]
+ [Class StaticMap  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/reference/maps/static-map)
+ [Overview  |  Maps Static API  |  Google Developers](https://developers.google.com/maps/documentation/maps-static/overview)
+ [Class DirectionFinder  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/reference/maps/direction-finder)
+ [The Directions API quickstart  |  Google Developers](https://developers.google.com/maps/documentation/directions/start)
+ [Encoded Polyline Algorithm Format  |  Google Maps Platform](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)