Google Apps Script 公式チュートリアルのプチ改造 (Quickstart: Menus and Custom Functions)

# はじめに
Google Apps Scriptの公式サイトのチュートリアル[「Quickstart: Menus and Custom Functions」](https://developers.google.com/apps-script/quickstart/custom-functions)を多くの人が写経していると思います。本記事では、チュートリアルの内容の理解を深めるために、自分なりに「プチ改造」した内容を記載します。

# プチ改造内容
オリジナルのソースコードに、以下２点の改造を加えます。

1. 日本語化する
2. ルート検索のモードを変更する

# ゴール
以下のように、オリジナルでは英語で表示されていたものを日本語にします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73241/d3e9d159-19dd-a3b4-e4dc-1c282a98ee80.png)

また、ルート検索の結果も、日本語で表示されるようにします。さらに、オリジナルでは、ルート検索でのモードが「車による運転」ですが、これも「公共交通機関」に変更します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73241/e8dd3fed-8aff-237d-2d67-540091c89e46.png)

# 手順
## オリジナルのチュートリアル実施
まずは、オリジナルのチュートリアルを以下のサイトに従って行います。

[Quickstart: Menus and Custom Functions](https://developers.google.com/apps-script/quickstart/custom-functions)

## 日本語化する
公式チュートリアルは、当然のことながら英語の表示となります。ルート検索の結果も以下のように英語で表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73241/2805caad-f728-c990-2150-b1f43f7be3cb.png)

これを日本語にするには、「DirectionFinder」オブジェクトに以下のように言語設定をします。

```javascript:コード.gs
var directionFinder = Maps.newDirectionFinder();
〜中略〜
directionFinder.setLanguage('ja'); // 言語設定を追加する
```

また、シート上に表示される文言も適宜、日本語に変更します。

## ルート検索のモードを変更する
オリジナルでは、ルート検索でのモードが「車による運転(DRIVING)」ですが、これも「公共交通機関(TRANSIT)」に変更します。

また、モードが「公共交通機関(TRANSIT)」の場合は、出発時刻または到着時刻を指定する必要があります。こちらの例では、出発時刻として現在時刻を設定しています。

```javascript:コード.gs
var directionFinder = Maps.newDirectionFinder();
〜中略〜
directionFinder.setMode(Maps.DirectionFinder.Mode.TRANSIT); // 「公共交通機関(TRANSIT)に変更」
directionFinder.setDepart(new Date()); // 出発時刻に現在時刻を設定

```
なお、モードに指定できるのは、以下の値となります。
* DRIVING
* WALKING
* BICYCLING
* TRANSIT

詳細は、公式リファレンスを参照してください。
[Google Apps Script - REFERENCE - Maps - Enum Mode](https://developers.google.com/apps-script/reference/maps/mode)

# ソースコード
以下にすべてのソースコードを記載します。

```javascript:コード.gs
function onOpen() {
  var spreadsheet = SpreadsheetApp.getActive();
  var menuItems = [
    {name: 'シートを準備...', functionName: 'prepareSheet_'},
    {name: 'ルート生成...', functionName: 'generateStepByStep_'},
  ];
  spreadsheet.addMenu('ルート検索', menuItems);
}
    
function metersToMiles(meters) {
  if (typeof meters != 'number') {
    return null;
  }
  return meters / 1000 * 0.6212371;
}
function drivingDistance(origin, destination) {
  var directions = getDirections_(origin, destination);
  return directions.routes[0].legs[0].distance.value;
}
function prepareSheet_() {
  var sheet = SpreadsheetApp.getActiveSheet().setName('設定');
  var headers = [
    '始点',
    '終点',
    '距離 (メートル)',
    '距離 (マイル)'
  ];
  var initialDate = [
    '350 5th Ave, New York, NY 10118',
    '405 Lexington Ave, New York, NY 10174'
  ];
  sheet.getRange('A1:D1').setValues([headers]).setFontWeight('bold');
  sheet.getRange('A2:B2').setValues([initialDate]);
  sheet.setFrozenRows(1);
  sheet.autoResizeColumns(1, 4);
}

function generateStepByStep_() {
  var spreadsheet = SpreadsheetApp.getActive();
  var settingsSheet = spreadsheet.getSheetByName('設定');
  settingsSheet.activate();

  var selectedRow = Browser.inputBox('ルート生成',
                                 'ルート生成する対象の行番号を指定してください' + 
                                     ' (例: "2"):',
                                 Browser.Buttons.OK_CANCEL);
  if (selectedRow == 'cancel') {
    return;
  }

  var rowNumber = Number(selectedRow);
  if (isNaN(rowNumber) || rowNumber < 2 ||
      rowNumber > settingsSheet.getLastRow() ) {
    Browser.msgBox('Error',
                  Utilities.formatString('Row "%s" is not valid.', selectedRow),
                   Browser.Buttons.OK);
    return;
  }
  var row = settingsSheet.getRange(rowNumber, 1, 1, 2);
  var rowValues = row.getValues();
  var origin = rowValues[0][0];
  var destination = rowValues[0][1];
  if (!origin || !destination) {
    Browser.msgBox('Error', 'Row does not contain two addresses.',
                   Browser.Buttons.OK);
    return;
  }
  var directions = getDirections_(origin, destination);
  
  var sheetName = 'ルート 行: ' + rowNumber;
  var directionsSheet = spreadsheet.getSheetByName(sheetName);
  if (directionsSheet) {
    directionsSheet.clear();
    directionsSheet.activate();
  } else {
    directionsSheet = 
      spreadsheet.insertSheet(sheetName, spreadsheet.getNumSheets());
  }
  var sheetTitle = Utilities.formatString('ルート[始点(%s) ～ 終点(%s)])',
                                          origin, destination);
  var headers  = [
    [sheetTitle, '', ''],
    ['ステップ', '距離 (メートル)', '距離 (マイル)']
  ];
  var newRows = [];
  for (var i = 0; i < directions.routes[0].legs[0].steps.length; i++) {
    var step = directions.routes[0].legs[0].steps[i];
    
    var instructions = step.html_instructions.replace(/<br|<div.*?>/g, '\n')
    .replace(/<.*?>/g, '');
    newRows.push([
      instructions,
      step.distance.value
    ]);
  }
  directionsSheet.getRange(1, 1, headers.length, 3).setValues(headers);
  directionsSheet.getRange(headers.length + 1, 1, newRows.length, 2)
    .setValues(newRows);
  directionsSheet.getRange(headers.length + 1, 3, newRows.length, 1)
    .setFormulaR1C1('=METERSTOMILES(R[0]C[-1])');
  
  directionsSheet.getRange('A1:C1').merge().setBackground('#ddddee');
  directionsSheet.getRange('A1:2').setFontWeight('bold');
  directionsSheet.setColumnWidth(1, 500);
  directionsSheet.getRange('B2:C').setVerticalAlignment('top');
  directionsSheet.getRange('C2:C').setNumberFormat('0.00');
  var stepsRange = directionsSheet.getDataRange()
    .offset(2, 0, directionsSheet.getLastRow() -2);
  setAlternatingRowBackgroundColors_(stepsRange, '#ffffff', '#eeeeee');
  directionsSheet.setFrozenRows(2);
  SpreadsheetApp.flush();
}

function setAlternatingRowBackgroundColors_(range, oddColor, evenColor) {
  var backgrounds = [];
  for (var row = 1; row <= range.getNumRows(); row++) {
    var rowBackgrounds = [];
    for (var column = 1; column <= range.getNumColumns(); column++) {
      if (row % 2 == 0) {
        rowBackgrounds.push(evenColor);
      } else {
        rowBackgrounds.push(oddColor);
      }
    }
    backgrounds.push(rowBackgrounds);
  }
  range.setBackgrounds(backgrounds);
}

function getDirections_(origin, destination) {
  var directionFinder = Maps.newDirectionFinder();
  directionFinder.setOrigin(origin);
  directionFinder.setDestination(destination);
  directionFinder.setMode(Maps.DirectionFinder.Mode.TRANSIT);
  directionFinder.setDepart(new Date());
  directionFinder.setLanguage('ja');

  var directions = directionFinder.getDirections();
  if (directions.status !== 'OK') {
    throw directions.error_message;
  }
  return directions;
}
```

# 最後に
プログラムを学ぶうえで「写経」はとても重要です。しかし、ソースコードをより深く理解するためには、「写経」した内容にオリジナルの改造を加えることがとても有効です。
今回もたいした改造はしていないですが、単に「写経」するよりは、確実に自分の身になっていると実感します。
みなさんもぜひ、プチ改造しましょう！