Calcular distância percorrida e converter metros em milhas

Nível de codificação: iniciante
Duração: 10 minutos
Tipo de projeto: função personalizada e automação com um menu personalizado

Objetivos

  • Entenda o que a solução faz.
  • Entenda o que os serviços do Apps Script fazem na solução.
  • Configure o script.
  • Execute o script.

Sobre esta solução

Usando funções personalizadas, é possível calcular a distância de carro entre dois locais e converter a distância de metros para milhas. Uma automação adicional oferece um menu personalizado que permite adicionar instruções detalhadas do endereço de partida ao endereço de destino em uma nova planilha.

Captura de tela de rotas em uma página.

Como funciona

O script usa duas funções personalizadas e uma automação.

  • A função drivingDistance(origin, destination) usa o serviço do Maps para calcular as direções de carro entre dois locais e retornar a distância entre os dois endereços em metros.
  • A função metersToMiles(meters) calcula o número equivalente de milhas para um determinado número de metros.
  • A automação solicita que o usuário insira qual linha de endereços de início e fim para calcular as rotas e adiciona as rotas detalhadas a uma nova planilha.

Serviços do Apps Script

Esta solução usa os seguintes serviços:

  • Serviço de planilhas: adiciona o menu personalizado, adiciona dados de demonstração para testar essa solução e formata as novas planilhas quando o script adiciona rotas de carro.
  • Serviço base: usa a classe Browser para pedir que o usuário insira um número de linha para receber direções e alerta o usuário se um erro ocorrer.
  • Serviço de utilitários: atualiza strings de modelo com informações especificadas pelo usuário.
  • Serviço do Maps: recebe rotas detalhadas do Google Maps do endereço de partida até o de destino.

Pré-requisitos

Para usar este exemplo, você precisa dos seguintes pré-requisitos:

  • Uma Conta do Google (as contas do Google Workspace podem exigir a aprovação do administrador).
  • Um navegador da Web com acesso à Internet.

Configurar o script

  1. Faça uma cópia da planilha Calculate driving distance and convert meters to miles. O projeto do Apps Script para essa solução é anexado à planilha.
    Fazer uma cópia
  2. Para adicionar cabeçalhos e dados de demonstração à página, clique em Instruções > Preparar página. Talvez seja necessário atualizar a página para que esse menu personalizado apareça.
  3. Quando solicitado, autorize o script. Se a tela de consentimento do OAuth mostrar o aviso Este app não está verificado, continue selecionando Avançado > Acessar {Nome do projeto} (inseguro).

  4. Clique em Directions > Prepare sheet novamente.

Executar o script

  1. Na célula C2, insira a fórmula =DRIVINGDISTANCE(A2,B2) e pressione Enter. Se você estiver em um local que usa vírgulas decimais, talvez seja necessário inserir =DRIVINGDISTANCE(A2;B2).
  2. Na célula D2, insira a fórmula =METERSTOMILES(C2) e pressione Enter.
  3. (Opcional) Adicione mais linhas de endereços de início e fim e copie as fórmulas nas colunas C e D para calcular as distâncias de carro entre vários lugares.
  4. Clique em Rotas > Gerar instruções detalhadas.
  5. Na caixa de diálogo, insira o número da linha dos endereços para os quais você quer gerar direções e clique em OK.
  6. Confira as instruções de direção na nova planilha criada pelo script.

Revisar o código

Para revisar o código do Apps Script para essa solução, clique em Ver código-fonte abaixo:

Acessar o código-fonte

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 (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;
}

Colaboradores

Esse exemplo é mantido pelo Google com a ajuda de especialistas em desenvolvimento do Google.

Próximas etapas