計算行車距離 &;將公尺轉換為英里

程式設計程度:初學者
時間長度:10 分鐘
專案類型自訂函式和使用自訂選單的自動化功能

目標

  • 瞭解解決方案的功能。
  • 瞭解解決方案中的 Apps Script 服務功能。
  • 設定指令碼。
  • 執行指令碼。

認識這項解決方案

您可以使用自訂函式計算兩個地點之間的行車距離,並將距離從公尺換算為英里。此外,這項自動化功能還提供自訂選單,可讓您在新工作表中新增從起點地址到終點地址的逐步路線。

工作表中的行車路線螢幕截圖。

運作方式

這項指令碼會使用 2 個自訂函式和 1 個自動化動作。

  • drivingDistance(origin, destination) 函式會使用地圖服務計算兩個地點之間的行車路線,並以公尺為單位傳回兩個地址之間的距離。
  • metersToMiles(meters) 函式會計算指定公尺數的等值英里數。
  • 自動化程序會提示使用者輸入要計算行車路線的起始和結束地址列,並將逐步行車路線新增至新工作表。

Apps Script 服務

這項解決方案會使用下列服務:

  • 試算表服務:新增自訂選單、新增示範資料來測試這項解決方案,以及在指令碼新增行車路線時,格式化新試算表。
  • 基本服務:使用 Browser 類別提示使用者輸入路線編號,並在發生錯誤時提醒使用者。
  • 公用程式服務:使用使用者指定的資訊更新範本字串。
  • 地圖服務:從起點地址到終點地址,取得 Google 地圖的逐步路線指示。

必要條件

如要使用這個範例,您必須符合下列先決條件:

  • Google 帳戶 (Google Workspace 帳戶可能需要管理員核准)。
  • 可連上網際網路的網路瀏覽器。

設定指令碼

  1. 複製「Calculate driving distance and convert meters to miles」(計算行車距離並將公尺換算為英里) 試算表。這個解決方案的 Apps Script 專案已附加至試算表。
    建立副本
  2. 如要在工作表中新增標題和範例資料,請依序點選「操作說明」>「準備工作表」。你可能需要重新整理頁面,才能看到這個自訂選單。
  3. 出現提示訊息時,請授權執行指令碼。 如果 OAuth 同意畫面顯示「這個應用程式未經驗證」警告,請依序選取「進階」>「前往『{專案名稱}』(不安全)」,繼續操作。

  4. 依序點選「路線」>「準備紙張」

執行指令碼

  1. C2 儲存格中輸入公式 =DRIVINGDISTANCE(A2,B2),然後按下 Enter 鍵。 如果您所在地區使用小數點逗號,可能需要改為輸入 =DRIVINGDISTANCE(A2;B2)
  2. D2 儲存格中輸入公式 =METERSTOMILES(C2),然後按下 Enter 鍵。
  3. (選用) 新增其他起始和結束地址列,並複製 CD 欄中的公式,計算不同地點之間的行車距離。
  4. 依序按一下「規劃路線」> 「產生逐步路線」
  5. 在對話方塊中,輸入要產生路線的地址列號,然後按一下「確定」
  6. 查看指令碼建立的新工作表中的行車路線。

檢查程式碼

如要查看這項解決方案的 Apps Script 程式碼,請按一下下方的「查看原始碼」

查看原始碼

Code.gs

sheets/customFunctions/customFunctions.gs
/**
 * @OnlyCurrentDoc Limits the script to only accessing the current sheet.
 */

/**
 * A special function that runs when the spreadsheet is open, used to add a
 * custom menu to the spreadsheet.
 */
function onOpen() {
  try {
    const spreadsheet = SpreadsheetApp.getActive();
    const menuItems = [
      { name: "Prepare sheet...", functionName: "prepareSheet_" },
      { name: "Generate step-by-step...", functionName: "generateStepByStep_" },
    ];
    spreadsheet.addMenu("Directions", menuItems);
  } catch (e) {
    // TODO (Developer) - Handle Exception
    console.log(`Failed with error: %s${e.error}`);
  }
}

/**
 * A custom function that converts meters to miles.
 *
 * @param {Number} meters The distance in meters.
 * @return {Number} The distance in miles.
 */
function metersToMiles(meters) {
  if (typeof meters !== "number") {
    return null;
  }
  return (meters / 1000) * 0.621371;
}

/**
 * A custom function that gets the driving distance between two addresses.
 *
 * @param {String} origin The starting address.
 * @param {String} destination The ending address.
 * @return {Number} The distance in meters.
 */
function drivingDistance(origin, destination) {
  const directions = getDirections_(origin, destination);
  return directions.routes[0].legs[0].distance.value;
}

/**
 * A function that adds headers and some initial data to the spreadsheet.
 */
function prepareSheet_() {
  try {
    const sheet = SpreadsheetApp.getActiveSheet().setName("Settings");
    const headers = [
      "Start Address",
      "End Address",
      "Driving Distance (meters)",
      "Driving Distance (miles)",
    ];
    const 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);
  } catch (e) {
    // TODO (Developer) - Handle Exception
    console.log(`Failed with error: %s${e.error}`);
  }
}

/**
 * Creates a new sheet containing step-by-step directions between the two
 * addresses on the "Settings" sheet that the user selected.
 */
function generateStepByStep_() {
  try {
    const spreadsheet = SpreadsheetApp.getActive();
    const settingsSheet = spreadsheet.getSheetByName("Settings");
    settingsSheet.activate();

    // Prompt the user for a row number.
    const selectedRow = Browser.inputBox(
      "Generate step-by-step",
      "Please enter the row number of" +
        " the" +
        " addresses to use" +
        ' (for example, "2"):',
      Browser.Buttons.OK_CANCEL,
    );
    if (selectedRow === "cancel") {
      return;
    }
    const rowNumber = Number(selectedRow);
    if (
      Number.isNaN(rowNumber) ||
      rowNumber < 2 ||
      rowNumber > settingsSheet.getLastRow()
    ) {
      Browser.msgBox(
        "Error",
        Utilities.formatString('Row "%s" is not valid.', selectedRow),
        Browser.Buttons.OK,
      );
      return;
    }

    // Retrieve the addresses in that row.
    const row = settingsSheet.getRange(rowNumber, 1, 1, 2);
    const rowValues = row.getValues();
    const origin = rowValues[0][0];
    const destination = rowValues[0][1];
    if (!origin || !destination) {
      Browser.msgBox(
        "Error",
        "Row does not contain two addresses.",
        Browser.Buttons.OK,
      );
      return;
    }

    // Get the raw directions information.
    const directions = getDirections_(origin, destination);

    // Create a new sheet and append the steps in the directions.
    const sheetName = `Driving Directions for Row ${rowNumber}`;
    let directionsSheet = spreadsheet.getSheetByName(sheetName);
    if (directionsSheet) {
      directionsSheet.clear();
      directionsSheet.activate();
    } else {
      directionsSheet = spreadsheet.insertSheet(
        sheetName,
        spreadsheet.getNumSheets(),
      );
    }
    const sheetTitle = Utilities.formatString(
      "Driving Directions from %s to %s",
      origin,
      destination,
    );
    const headers = [
      [sheetTitle, "", ""],
      ["Step", "Distance (Meters)", "Distance (Miles)"],
    ];
    const newRows = [];
    for (const step of directions.routes[0].legs[0].steps) {
      // Remove HTML tags from the instructions.
      const 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])");

    // Format the new sheet.
    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");
    const stepsRange = directionsSheet
      .getDataRange()
      .offset(2, 0, directionsSheet.getLastRow() - 2);
    setAlternatingRowBackgroundColors_(stepsRange, "#ffffff", "#eeeeee");
    directionsSheet.setFrozenRows(2);
    SpreadsheetApp.flush();
  } catch (e) {
    // TODO (Developer) - Handle Exception
    console.log(`Failed with error: %s${e.error}`);
  }
}

/**
 * Sets the background colors for alternating rows within the range.
 * @param {Range} range The range to change the background colors of.
 * @param {string} oddColor The color to apply to odd rows (relative to the
 *     start of the range).
 * @param {string} evenColor The color to apply to even rows (relative to the
 *     start of the range).
 */
function setAlternatingRowBackgroundColors_(range, oddColor, evenColor) {
  const backgrounds = [];
  for (let row = 1; row <= range.getNumRows(); row++) {
    const rowBackgrounds = [];
    for (let column = 1; column <= range.getNumColumns(); column++) {
      if (row % 2 === 0) {
        rowBackgrounds.push(evenColor);
      } else {
        rowBackgrounds.push(oddColor);
      }
    }
    backgrounds.push(rowBackgrounds);
  }
  range.setBackgrounds(backgrounds);
}

/**
 * A shared helper function used to obtain the full set of directions
 * information between two addresses. Uses the Apps Script Maps Service.
 *
 * @param {String} origin The starting address.
 * @param {String} destination The ending address.
 * @return {Object} The directions response object.
 */
function getDirections_(origin, destination) {
  const directionFinder = Maps.newDirectionFinder();
  directionFinder.setOrigin(origin);
  directionFinder.setDestination(destination);
  const directions = directionFinder.getDirections();
  if (directions.status !== "OK") {
    throw directions.error_message;
  }
  return directions;
}

貢獻者

這個範例由 Google 維護,並由 Google 開發人員專家協助。

後續步驟