Blow Up by Black Swan

GAS(Google Apps Script)とスプレッドシート Part3(クイックスタート編)

前々回ではGASからスプレッドシートを操作する基本的な方法について、前回はスプレッドシートでカスタムファンクションを作成する方法について記事を書きました。今回は、GASの公式リファレンスにある「5minute Quickstarts」の「Menus and Custom Functions」のコードを参照して、応用的な使い方についてまとめました。

公式リファレンスは英語のみのため、この記事は英語が苦手な方の補助資料になればと思い書きました。この記事が一人でも多くの方の参考になれば幸いです。

※ 当記事は2019年8月現在のGASの公式リファレンスに基づいています。GASの今後のアップデート等によっては、記事内容と公式リファレンスに齟齬が生じる場合がございますので、あらかじめGASの最新状況等を確認頂ければと思います。

1. クイックスタートの内容

クイックスタートは構造的に次の3つからなっています。

  1. トリガー関数を使ってツールバーにカスタムメニューを追加する
  2. カスタムメニュー内のサブメニューに関数を紐付け、サブメニューを選択すると関数が実行される様にする
  3. カスタムメニューから実行する関数の追加

1-1. クイックスタート実行の手順と結果

このクイックスタートのコードの基本的な実行フローとそれぞれの結果は以下になります。

  1. スプレッドシートを開くとカスタムメニュー「Directions」がツールバーに表示される(onOpen関数の実行)
  2. 「Directions」メニュー内のサブメニュー「Prepare sheet…」を選択(prepareSheet_関数の実行)
    1. アクティブ状態のシートの名前が”Setting”に変更される
    2. 2拠点(出発地と目的地)の入力用にシートが整形される
    3. C2セルに”=drivingDistance(A2, B2)”を入力することで2拠点間のメートル距離を算出(drivingDistance関数の実行)
    4. D2セルに”=metersToMiles(C2)”と入力することで2拠点間のマイル距離を算出(metersToMiles関数の実行)
  3. 「Directions」内サブメニューで「Generate step-by-step…」を選択(generateStepByStep_関数の実行)
    1. “Setting”シートからルートを求めたい行をで選択
    2. その経路の細かなルートを算出(getDirections_関数の実行)
    3. 新規にシートを作成し、そのルートを入力
    4. シートを整形(setAlternatingRowBackgroundColors_関数の実行)
  4. 完了

基本的な実行フローは以上になります。複数の関数を定義することで散乱したコードにならない様になっています。GASの基本がわかっていれば内容自体はそこまで複雑ではありません。このクイックスタートのポイントは以下になります。

  • GASのトリガー関数(ここではonOpen関数)を利用することでスプレッドシートの機能を拡張できる
  • 今回の場合、ツールバーに独自のカスタムメニューが追加されている
  • そのカスタムメニューからカスタムファンクションを実行できる
  • GASライブラリを使うことで、新規シートの作成、シート名変更、背景色や表示固定列の指定など、様々な編集ができる
  • Mapsサービスを使うことで簡単に経路などの地図関連データを利用できる

なお、アンダースコアがついている関数はGAS内で利用されるプライベート関数となり、セルで利用することはできません。

2. コード内容

それではコードについて説明していきます。コードについては上記実行フローを参照し、下記のセグメントに分けてそれぞれ関連する関数を説明していきます。

  1. カスタムメニューの追加: onOpen関数
  2. 設定用シートの作成: prepareSheet_関数
  3. 経路の算出(とその補助関数)
    • generateStepByStep_関数
    • getDirections_関数
    • setAlternatingRowBackgroundColors_関数
  4. セルで利用するカスタムファンクション
    • drivingDistance関数
    • metersToMiles関数

2-1. カスタムメニューの追加: onOpen関数

この関数は、このコード内ではツールバーにカスタムメニューを作成するために利用されます。このonOpen関数は、GASのトリガー関数の一種で、「ユーザーが編集許可をもつスプレッドシートやドキュメント、プレゼンテーション、フォームなどをオープンした時」に実行されます。スプレッドシートを「開いた後」ではなく「開いた時」に実行されるため、スプレッドシートが完全に開かれた時にはカスタムメニューがツールバーに表示されていることになります。関数の内容はいたってシンプルです。

function onOpen() {
  var spreadsheet = SpreadsheetApp.getActive();
  var menuItems = [
    {name: 'Prepare sheet...', functionName: 'prepareSheet_'},  // prepareSheet_...下記で定義
    {name: 'Generate step-by-step...', functionName: 'generateStepByStep_'}  // generateStepByStep_...下記で定義
  ];
  spreadsheet.addMenu('Directions', menuItems);
}

この関数についてまとめると以下になります。

  • 関数の役割: ツールバーにカスタムメニューを表示する
  • 関数・メソッド
    • onOpen(e)…ファイルが開かれた時に実行されるトリガー関数
      • 引数-e: イベントオブジェクト
    • SpreadSheetOBJ.addMenu( name, subMenus )…スプレッドシートにカスタムメニューを追加するメソッド
      • 注意: シートオブジェクトではなく、スプレッドシートオブジェクトのメソッド
      • 引数-name: カスタムメニューの名前
      • 引数-subMenus: カスタムメニューで表示されるサブメニュー。{name: サブメニュー名, functionName: 関数名}を要素とする配列

2-2. 設定用シートの作成: prepareSheet_関数

これは距離やルートを調べたい2地点を設定するためのシートを作成する関数です。これを実行するとアクティブな状態にあるシートの名称が”Setting”に変更され、出発地と目的地の2地点をを設定するためのテーブルが作成されます。最初の行に初期値としてアメリカの住所が入力されますが、その下段から任意の地点を設定することができます。コードは以下になります。

function prepareSheet_() {
  // アクティブなシートの名称を変更
  var sheet = SpreadsheetApp.getActiveSheet().setName('Settings');

  // 各列の見出し
  var headers = [
    'Start Address',
    'End Address',
    'Driving Distance (meters)',
    'Driving Distance (miles)'];
  
  // 初期データ
  var initialData = [
    '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([initialData]);                    // 初期データの入力
  sheet.setFrozenRows(1);                                              // 固定表示列の設定
  sheet.autoResizeColumns(1, 4);                                       // 列サイズの変更
}

この関数のまとめは以下になります。

  • 役割: サブメニューで利用される関数で、スプレッドシートにヘッダーと複数の初期データを加える
  • 関数・メソッド
    • SheetOBJ.setName( sheetName )…シート名をセットする関数(現行のシート名が変更される)
    • RangeOBJ.setFontWeight( fontWeight )…フォントの太字設定をセットする
    • sheetOBJ.setFrozenRows( rows )…指定した列で表示固定の列とする
    • sheetOBJ.autoResizeColumns( startColumn, numColumns )…コンテンツが収まるようにカラムを自動調整する

2-3. 細かなルートとその距離の算出

ここの主関数はgenerateStepByStep_関数ですが、これはツールバーの「Directions」のサブメニュー「Generate step-by-step…」を選択することで呼び出されます。この関数が実行されるとまず経路を調べたい任意の地点が記載された行番号の入力をもとめるメッセージボックスが表示されます。そして、そこで選択した2地点間のルートを算出し、新しいシートに入力されます。この関数は補助関数として2つの関数を利用しています。

generateStepByStep_関数

このgenerateStepByStep_関数がサブメニューで実行される関数です。これを実行すると”Setting”シートから経路を求めたい2地点が書かれた行番号を入力するよう求められます。そして、そこで選択した地点間の経路を算出します。長いコードにはなりますが、順に追っていけば理解できると思います。

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

  // ユーザーに行番号の記入を促す
  var selectedRow = Browser.inputBox('Generate step-by-step',
      'Please enter the row number of the addresses to use' +
      ' (for example, "2"):',
      Browser.Buttons.OK_CANCEL);  // OKとキャンセルの両方のボタンを表示する
  
  // キャンセルの場合('cancel'と入力) => 何も返さない
  if (selectedRow == 'cancel') {
    return;
  }
  var rowNumber = Number(selectedRow);  // 数値型に変換
  
  // 入力値がNaNである場合 or 2未満である場合 => 何も返さない
  if (isNaN(rowNumber) || rowNumber < 2 ||
      rowNumber > settingsSheet.getLastRow()) {
    Browser.msgBox('Error',
        Utilities.formatString('Row "%s" is not valid.', selectedRow),  // promptのテキストの生成
        Browser.Buttons.OK);  // OKボタンのみ
    return;
  }

  // 指定された行の住所を取得
  var row = settingsSheet.getRange(rowNumber, 1, 1, 2);
  var rowValues = row.getValues();
  var origin = rowValues[0][0];  // 出発地
  var destination = rowValues[0][1];  // 目的地
  
  // 出発地 or 目的地のどちらかが入力されていない場合 => エラーメッセージを出し終わり
  if (!origin || !destination) {
    Browser.msgBox('Error', 'Row does not contain two addresses.',
        Browser.Buttons.OK);
    return;
  }

  // 列の出発地、目的地情報を取得(getDirections_関数の実行)
  var directions = getDirections_(origin, destination);

  // 新しいシートを作り、出発地から目的地までの細かなルートを入力
  var sheetName = 'Driving Directions for Row ' + rowNumber;
  var directionsSheet = spreadsheet.getSheetByName(sheetName);
  
  if (directionsSheet) {  // directionSheetが存在する場合
    directionsSheet.clear();
    directionsSheet.activate();
  } else {                // directionSheetが存在しない場合
    directionsSheet = spreadsheet.insertSheet(sheetName, spreadsheet.getNumSheets());
  }
  var sheetTitle = Utilities.formatString('Driving Directions from %s to %s',
      origin, destination);
  var headers = [
    [sheetTitle, '', ''],  // 1行目
    ['Step', 'Distance (Meters)', 'Distance (Miles)']  // 2行目
  ];
  // 各経路を格納するための配列
  var newRows = [];
  
  // 経路のステップの数だけ繰り返し、それぞれの距離を求める
  for (var i = 0; i < directions.routes[0].legs[0].steps.length; i++) {
    var step = directions.routes[0].legs[0].steps[i];
    // html_instructionsからHTMLタグを削除し、
    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();
}

この関数をまとめると以下になります。

  • 役割: “Setting”シート上の2つの地点のルートを新しいシートに入力
  • 関数・メソッド
    • SheetOBJ.activate()…オブジェクトシートをアクティブにする
    • Browser.inputBox( title, prompt, buttons )…ユーザブラウザ上にインプットボックスを表示する
      • title…inputBoxのタイトル
      • prompt…説明書き
      • buttons…決定ボタンの種類。Okのみやキャンセルも表示させるなど
    • Browser.msgBox( title, prompt, buttons )…ブラウザにメッセージボックスを表示する
      • title…メッセージボックスのタイトル
      • prompt…説明書き
      • button.…決定ボタンの種類
    • isNaN( args )…引数がNaNかチェックする(JavaScriptの関数)
    • SpreadSheetOBJ.insertSheet( sheetName, sheetIndex )…新しいシートを挿入する
      • sheetName…シートの名前
      • sheetIndex…シートを挿入する場所。先頭は0
    • SpreadSheetOBJ.getNumSheets()…シートの数を返す
    • Utilities.formatStrings( format, args )…文字列を生成する
    • RangeOBJ.setFormulaR1C1( formula )…対象のセルの数式をアップデートする。数式で計算される行と列は対象セルを基準に指定する
    • RangeOBJ.merge()…1つのセルに結合
    • RangeOBJ.setBackground( color )…背景色の指定
    • RangeOBJ.setFontWeight( fontWeight )…フォントウェイトの設定
    • SheetOBJ.setColumnWidth( columnPosition, width )…行の幅を規定
    • Range.setVerticalAlignment( alignment )…上下の配置を規定(top,middle,bottom,nullで設定をリセット)
    • Range.setNumberFormat( numberFormat )…数値型と時刻型のフォーマットを規定(参考: https://developers.google.com/sheets/api/guides/formats)
    • SheetOBJ.getDataRange()…データが存在するセル範囲を取得
    • Range.offset( rowOffset, columnOffset, numRows )…現在のセルを基準に指定した値分だけセルを移動させる
    • SpreadsheetApps.flush()…変更を一括してスプレッドシートに反映させる

getDirections_関数

getDirections_関数は、選択した2地点間の経路を求める関数です。戻り値は経路に関連するデータが格納されたJavaScriptオブジェクトです。オブジェクトにはかなり多くの情報が記載されているため、一部省略したものを下記に貼り付けてあります。

function getDirections_(origin, destination) {
  //
  var directionFinder = Maps.newDirectionFinder();
  directionFinder.setOrigin(origin);
  directionFinder.setDestination(destination);
  var directions = directionFinder.getDirections();
  if (directions.status !== 'OK') {
    throw directions.error_message;
  }
  return directions;
}

ポイントは以下になります。なお、2地点間のルートを求めるMapsサービスはGASで提供されているサービス(ライブラリ)の1つです。公式リファレンスはこちらになります。

  • 役割: 2つの住所間の経路を取得するために使われるヘルパー関数。GASのマップサービスを使う
  • 引数-origin: <文字列>出発地
  • 引数-destination: <文字列>目的地
  • 戻り値: <オブジェクト>経路のレスポンスオブジェクト
  • 関数・メソッド
    • Maps.newDirectionsFinder()…DirectionFinderオブジェクトを生成する
    • DirectionFinderOBJ…2拠点間の経路を取得するためのオブジェクト
    • DirectionFinderOBJ.setOrigin( address )…出発地の住所をセットする
    • DirectionFinderOBJ.setDestination( address )…目的地の住所をセットする
    • DirectionFinderOBJ.getDirections()…<オブジェクト>経路を取得する。出発地や目的地、経路などの情報が入ったオブジェクトを返す

また、ルートを求める、getDirectionsメソッドで取得するJavaScriptオブジェクトの記載内容は以下の様なものとなります。

{"routes":[
    {"summary":"E 32nd St and Third Ave",
     "copyrights":"Map data ©2019 Google",
     "legs":[
        {"duration":{"text":"10 mins",
                     "value":581},
         "start_location":{"lng":-73.98481629999999,
                           "lat":40.74811270000001},
         "distance":{"text":"1.1 mi",
                     "value":1814},
         "start_address":"350 5th Ave, New York, NY 10118, USA",
         "end_location":{"lng":-73.9757362,
                         "lat":40.7519078},
         "end_address":"405 Lexington Ave, New York, NY 10174, USA",
         "via_waypoint":[],
         "steps":[
            {"duration":{"text":"1 min",
                         "value":30},
             "start_location":{"lng":-73.98481629999999,
                               "lat":40.74811270000001},
              "distance":{"text":"394 ft",
                          "value":120},
              "travel_mode":"DRIVING",
              "html_instructions":"Head southwest on 5th Ave toward W 33rd St",
              "end_location":{"lng":-73.985504,
                              "lat":40.7471648},
              "polyline":{"points":"uruwFbdqbMbAp@xBtA"}
             },
             
             (中略)

         ],
         "traffic_speed_entry":[]
        }
    ],
    "warnings":[],
    "bounds":{"southwest":{"lng":-73.985504,
                           "lat":40.744452},
              "northeast":{"lng":-73.9739561,
                           "lat":40.7521288}
             },
    "overview_polyline":{"points":"uruwFbdqbM|DfCfCcIjFoPzBgHl@iByByAcBkAgDeAiD{BmGgEeDyB_Am@M^s@zB}@pCGRRNVP"},
    "waypoint_order":[]
   }
 ],
 "geocoded_waypoints":[
     {"types":["street_address"],
      "geocoder_status":"OK",
      "place_id":"ChIJn6wOs6lZwokRLKy1iqRcoKw"
     },
     {"types":["street_address"],
      "geocoder_status":"OK",
      "place_id":"ChIJAe73SQJZwokRHTRcoQpAhF8"
     }
 ],
 "status":"OK"
}

setAlternatingRowBackgroundColors_関数

これは、背景色を1行置きに設定することでシートをみやすくする関数です。

function setAlternatingRowBackgroundColors_(range, oddColor, evenColor) {
  var backgrounds = [];
  // 変数rowが実際の行数になるまで繰り返し
  for (var row = 1; row <= range.getNumRows(); row++) {
    var rowBackgrounds = [];
    // 変数columnが実際の列数になるまで繰り返し
    for (var column = 1; column <= range.getNumColumns(); column++) {
      // 列の背景色情報を配列rowBackgroundsに追加
      if (row % 2 == 0) {
        rowBackgrounds.push(evenColor);
      } else {
        rowBackgrounds.push(oddColor);
      }
    }
    // 行の背景色情報を配列backgroundsに追加
    backgrounds.push(rowBackgrounds);
  }
  range.setBackgrounds(backgrounds);
}

ポイントは以下になります。

  • 役割: 行の範囲内で交互に背景色をセットする
  • 引数-range: 背景色を設定するセル範囲
  • 引数-oddColor: 奇数行の色
  • 引数-evenColor: 偶数行の色
  • 関数・メソッド
    • RangeOBJ.getNumRows()…行数を取得する

2-4. セルで利用するカスタムファンクション

ここからはセルでも利用できるカスタムファンクションです。drivingDisance関数とmetersTomiles関数の2つがあります。

drivingDistance関数

この関数は引数で指定した出発地と目的地の2地点間の距離を求めます。

function drivingDistance(origin, destination) {
  var directions = getDirections_(origin, destination);  // プライベート関数のgetDirections_関数を実行
  return directions.routes[0].legs[0].distance.value;
}

ポイントは以下になります。

  • 役割: 2つの住所間の距離を求める
  • 引数-origin: <文字列型>出発点の住所
  • 引数-destination: <文字列型>到着地の住所
  • 戻り値: <数値型>メートルで表記された距離

metersToMiles関数

この関数はメートルをマイルに変換する関数です。アメリカを感じさせる関数です。日本で必要とする機会はほとんどないでしょう。

function metersToMiles(meters) {
  if (typeof meters != 'number') {
    return null;
  }
  return meters / 1000 * 0.621371;
}

ポイントは以下になります。

  • 役割: メートルをマイルに変換する。引数のmetersが数値型でない場合、nullが返されます
  • 引数-meters…メートルを表す数値
  • 戻り値: マイルを表す数値

3. 実行

この作成したGASプログラムを実行し、その結果のスクリーンショットを下記に貼り付けています。

(1) GASファイルを開く

gas-ss-quickstart1

(2)「Directions」メニューの「Prepare Sheet…」を選択 @@@ スクリーンショット2

gas-ss-quickstart2

・実行結果

gas-ss-quickstart3

(3) カスタムファンクション「drivingDistance」関数の実行

gas-ss-quickstart4

(4)カスタムファンクション「metersToMiles」関数の実行

gas-ss-quickstart5

(5) 「Directions」メニューの「Generate step-by-step…」を選択

gas-ss-quickstart6

(6) 対象とする経路の行番号を入力

gas-ss-quickstart7

・実行結果

gas-ss-quickstart8

(7) 初期値以外の経路を求める場合(今回は札幌駅から函館駅)

gas-ss-quickstart9

(8)「Generate step-by-step」を選択し、対象の経路である行番号「3」を選択

gas-ss-quickstart10

・実行結果

gas-ss-quickstart11

結論:札幌から函館は遠い

4. 最後に

今回は、GASからスプレッドシートを操作する方法について公式ドキュメントに書かれているクイックスタートについてまとめました。正直、自分も今まで知らないことが書かれており、公式ドキュメントを読んでやってみることの大切さを改めて実感しました。次回の記事については、先日友人の会社向けに作成した工場のシフト管理用のスプレッドシートにこのクイックスタートの様な改良を施した、利便性の高いシフト管理システムにを作成し、それを記事にしたいと考えています。

読んで頂き、ありがとうござました。この記事が参考になりましたら幸いです。