Criar um planejador de rotas com o Place Autocomplete e a API Routes

1. Visão geral

Seja para uma viagem de carro, um trajeto diário ou para se locomover em uma cidade movimentada, ir do ponto A ao ponto B é mais do que saber onde você quer chegar. Uma ferramenta confiável de geração de rotas é essencial.

Com a Plataforma Google Maps, você pode adicionar um mapa dinâmico ao seu aplicativo, permitir que os usuários insiram locais rapidamente com o preenchimento automático e mostrar rotas no mapa.

Este codelab orienta os desenvolvedores na criação de um aplicativo da Web usando a API Maps JavaScript, o Place Autocomplete e a API Routes. Você vai aprender a integrar várias APIs da Plataforma Google Maps em um tutorial personalizável.

O que você vai criar

Este codelab vai orientar você na criação de um aplicativo da Web usando HTML, CSS, JavaScript e um back-end Node.js.

Arquitetura do app da Web de planejamento de rotas

Web app de planejamento de trajetos

O que você vai aprender

  • Como ativar as APIs da Plataforma Google Maps
  • Como integrar um mapa dinâmico a um aplicativo da Web
  • Como integrar o serviço Place Autocomplete
  • Como solicitar uma rota usando a API Routes
  • Como mostrar o trajeto em um mapa dinâmico
  • Como criar um ID do mapa
  • Como adicionar marcadores avançados a um mapa dinâmico

O que é necessário

Código de amostra

A solução completa e o código detalhado estão disponíveis no GitHub. O código não inclui os pacotes do Node necessários. Instale as dependências necessárias antes de executar o código. Os detalhes dos pacotes necessários podem ser encontrados no arquivo package.json, explicado na etapa 3.

2. Configurar o projeto e ativar as APIs

Para a etapa de ativação, você precisa ativar a API Maps JavaScript, o Place Autocomplete e a API Routes.

Configurar a Plataforma Google Maps

Caso você ainda não tenha uma conta do Google Cloud Platform e um projeto com faturamento ativado, veja como criá-los no guia da Plataforma Google Maps.

  1. No Console do Cloud, clique no menu suspenso do projeto e selecione o projeto que você quer usar neste codelab. Selecionar projeto
  2. Ative as APIs da Plataforma Google Maps necessárias para este codelab na página da biblioteca de APIs Maps. Para fazer isso, siga as etapas deste vídeo ou desta documentação.
  3. Gere uma chave de API na página Credenciais do Console do Cloud. Siga as etapas indicadas neste vídeo ou nesta documentação. Todas as solicitações à Plataforma Google Maps exigem uma chave de API.

3. Configurar um projeto Node.js

Neste laboratório, vamos usar o Node.js para coletar a origem e o destino da Web e solicitar a rota pela API Routes.

Supondo que você já tenha instalado o Node.js, crie um diretório que será usado para executar este projeto:

$ mkdir ac_routes
$ cd ac_routes

Inicialize um novo pacote Node.js no diretório do seu aplicativo:

$ npm init

Esse comando solicita várias informações, como o nome e a versão do aplicativo. Por enquanto, basta pressionar RETURN para aceitar os valores padrão da maioria deles. O ponto de entrada padrão é index.js, mas você pode mudar para seu arquivo principal. Neste laboratório, o arquivo principal é function/server.js. Mais detalhes na etapa 6.

Além disso, instale seu framework e módulos preferidos. Este laboratório usa o framework da Web(Express) e o analisador de corpo(body-parser). Encontre mais detalhes no arquivo package.json.

4. Criar um mapa dinâmico

Agora que temos nosso back-end Node.js, vamos conferir as etapas necessárias para o lado do cliente.

  • Criar uma página HTML para o aplicativo
  • Criar um arquivo CSS para estilização
  • Carregar a API Google Maps JavaScript na página HTML
  • Cole a chave de API na tag script para autenticar o aplicativo.
  • Criar um arquivo JavaScript para processar a funcionalidade do aplicativo

Criar uma página HTML

  1. Crie um novo diretório na pasta do projeto(ac_routes neste caso).
     $ mkdir public
     $ cd public
    
  2. No diretório "public", crie o arquivo index.html.
  3. Copie o código a seguir em index.html
     <!DOCTYPE html>
     <html>
     <head>
       <title>GMP Autocomplete + Routes</title>
       <meta charset="utf-8">
       <link rel="stylesheet" type="text/css" href="style.css">
     </head>
     <body>
       <div class="container">
         <!-- Start of the container for map -->
         <div class="main">
           <div id="map"></div>
         </div>
         <!-- End of the container for map -->
       </div>
       </body>
     </html>
    

Criar um arquivo CSS

  1. Crie style.css no diretório "public"
  2. Copie o código a seguir em style.css:
     html, body {height: 100%;}
     body {
       background: #fff;
       font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
       font-style: normal;
       font-weight: normal;
       font-size:16px;
       line-height: 1.5;
       margin: 0;
       padding: 0;
     }
     .container {display:flex; width:90%; padding:100px 0; margin:0 auto;}
     .main {width:70%; height:800px;}
      #map {height:100%; border-radius:20px;}
    

Carregar a API Maps JavaScript

Neste laboratório, vamos usar a importação dinâmica de bibliotecas para carregar a API Maps JavaScript. Veja mais detalhes nesta página.

No index.html, copie o código a seguir antes da tag de fechamento "body". Substitua "YOUR_API_KEY" pela sua chave de API.

<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({key: "YOUR_API_KEY", v: "weekly"});</script>

Criar um arquivo JavaScript

  1. No diretório público, crie app.js
  2. Copie o código a seguir em app.js
     (function(){
       let map;
    
       async function initMap() {
           const { Map } = await google.maps.importLibrary('maps');
           map = new Map(document.getElementById('map'), {
               center: { lat: -34.397, lng: 150.644 },
               zoom: 8,
               mapId: 'DEMO_MAP_ID'
           });
       }
    
       initMap();
     }());
    

O DEMO_MAP_ID é um ID que pode ser usado em exemplos de código que exigem um ID do Maps. Esse ID não é destinado ao uso em aplicativos de produção e não pode ser usado para recursos que exigem estilização na nuvem. Neste laboratório, vamos precisar de um ID do mapa para os Marcadores Avançados na etapa final. Saiba mais detalhes sobre como criar um ID do mapa para seu aplicativo.

No index.html, vincule o app.js antes da tag de fechamento "body" e depois da tag de script de carregamento da API Maps JavaScript.

<script type="text/JavaScript" src="app.js"></script>

Exemplo de código completo

O código completo até este ponto está disponível no GitHub:step1_createDynamicMap

5. Inserir endereços de origem e destino

  • Adicione dois campos de texto no index.html para entrada de origem e destino
  • Importar a biblioteca Autocomplete
  • Vincular o serviço de preenchimento automático aos campos de texto de origem e destino

Adicionar campos de texto

No index.html, adicione o seguinte código como o primeiro filho do div com a classe container.

<div class="aside">
  <div class="inputgroup">
    <label for="origin">Start</label>
    <input type="text" id="origin" name="origin" class="input-location" placeholder="Enter an address">
  </div>
  <div class="inputgroup">
    <label for="origin">End</label>
    <input type="text" id="destination" name="destination" class="input-location" placeholder="Enter an address">
  </div>
</div>

Importar e ativar o preenchimento automático

A classe google.maps.places.Autocomplete é um widget que fornece previsões de lugares com base na entrada de texto de um usuário. Ele se conecta a um elemento de entrada do tipo texto e aguarda a entrada de texto nesse campo. A lista de previsões é apresentada como um menu suspenso e é atualizada conforme o texto é inserido.

No app.js, adicione o seguinte código após a inicialização do mapa:

let placeIds = [];
async function initPlace() {
  const { Autocomplete } = await google.maps.importLibrary('places');
  let autocomplete = [];
  let locationFields = Array.from(document.getElementsByClassName('input-location'));
  //Enable autocomplete for input fields
  locationFields.forEach((elem,i) => {
      autocomplete[i] = new Autocomplete(elem);
      google.maps.event.addListener(autocomplete[i],"place_changed", () => {
          let place = autocomplete[i].getPlace();
          if(Object.keys(place).length > 0){
              if (place.place_id){
                  placeIds[i] = place.place_id; //We use Place Id in this example
              } else {
                  placeIds.splice(i,1); //If no place is selected or no place is found, remove the previous value from the placeIds.
                  window.alert(`No details available for input: ${place.name}`);
                  return;
              }
          }
      });
  });
}
initPlace();

Depois que um usuário seleciona um lugar na lista de previsões de preenchimento automático, um detalhe do resultado do lugar pode ser recuperado usando o método getPlace(). O resultado do lugar contém muitas informações sobre um lugar. Neste laboratório, vamos usar "place_id" para identificar o lugar selecionado. IDs de lugares identificam um local de modo exclusivo no banco de dados do Google Places e no Google Maps. Saiba mais sobre IDs de lugar.

Adicionar estilos relevantes

No style.css, adicione o seguinte código:

.aside {width:30%; padding:20px;}
.inputgroup {margin-bottom:30px;}
.aside label {display:block; padding:0 10px; margin-bottom:10px; font-size:18px; color:#666565;}
.aside input[type=text] {width:90%;padding:10px; font-size:16px; border:1px solid #e6e8e6; border-radius:10px;}

Exemplo de código completo

O código completo até este ponto está disponível no GitHub:step2_inputAddress

6. Solicitar o trajeto

  • Adicione um botão "Receber uma rota" ao index.html para iniciar a solicitação de rota.
  • Esse botão aciona o envio de dados de origem e destino para o serviço Node.js.
  • O serviço Node.js envia uma solicitação para a API Routes
  • A resposta da API é retornada ao lado do cliente para exibição.

Com a origem e o destino definidos e um mapa dinâmico pronto, é hora de traçar a rota. A API Routes, a próxima geração do serviço de rotas e matriz de distância otimizado para performance, vem para ajudar. Neste laboratório, vamos usar o Node.js para coletar a origem e o destino da Web e solicitar a rota pela API Routes.

No index.html, adicione um botão "Receber uma rota" antes da tag de fechamento de div com a classe aside :

<div class="inputgroup">
  <button id="btn-getroute">Get a route</button>
</div>

No style.css, adicione a seguinte linha:

.aside button {padding:20px 30px; font-size:16px; border:none; border-radius:50px; background-color:#1a73e8; color:#fff;}

Em app.js, adicione o código abaixo para enviar dados de origem e destino ao serviço Node.js:

function requestRoute(){
  let btn = document.getElementById('btn-getroute');
  btn.addEventListener('click', () => {
    //In this example, we will extract the Place IDs from the Autocomplete response
    //and use the Place ID for origin and destination
    if(placeIds.length == 2){
        let reqBody = {
            "origin": {
                "placeId": placeIds[0]
            },
            "destination": {
                "placeId": placeIds[1]
            }
        }

        fetch("/request-route", {
            method: 'POST',
            body: JSON.stringify(reqBody),
            headers: {
                "Content-Type": "application/json"
            }
        }).then((response) => {
            return response.json();
        }).then((data) => {
            //Draw the route on the map
            //Details will be covered in next step
            renderRoutes(data);
        }).catch((error) => {
            console.log(error);
        });
    } else {
        window.alert('Location must be set');
        return;
    }
  });
}

requestRoute();

renderRoutes() é a função que vamos usar para desenhar a rota no mapa. Vamos abordar os detalhes na próxima etapa.

Como criar o servidor

No diretório do projeto(ac_routes, neste caso), crie uma pasta chamada "function". Dentro dessa pasta, crie um arquivo chamado server.js. O arquivo atua como o ponto de entrada do projeto, que é configurado ao configurar o projeto do Node.js, processando três funções principais:

  1. Coleta de dados do cliente da Web
  2. Enviar solicitações para a API Routes
  3. Retornar a resposta da API para o lado do cliente

Copie o código abaixo em server.js. Substitua "YOUR_API_KEY" pela sua chave de API. Para aumentar a segurança da chave de API, recomendamos usar uma chave diferente para o back-end. Consulte Orientações de segurança.

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

const port  = 8080;
const urlencodedParser = bodyParser.urlencoded({extended:true}); 

function main() {
  app.use('/', express.static('public'));
  app.use(urlencodedParser);
  app.use(express.json());

  app.post('/request-route', (req,res) => {    
    fetch("https://routes.googleapis.com/directions/v2:computeRoutes", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Goog-Api-Key": "YOUR_API_KEY",
        "X-Goog-FieldMask": "*"
      },
      body: JSON.stringify(req.body)
    }).then((response) => {
      return response.json();
    }).then((data) => {
      if('error' in data){
        console.log(data.error);
      } else if(!data.hasOwnProperty("routes")){
        console.log("No route round");
      } else {
        res.end(JSON.stringify(data));
      }
    }).catch((error) => {
      console.log(error)
    });
  });

  app.listen(port, () => {
      console.log('App listening on port ${port}: ' + port);
      console.log('Press Ctrl+C to quit.');
  });
}

main();

Encontre mais detalhes sobre a API Routes em como receber um trajeto com a API Routes.

Executar o código

Execute o código abaixo na linha de comando:

$ node function/server.js

Abra o navegador e acesse http://127.0.0.1:8080/index.html. A página do aplicativo vai aparecer. Até esta etapa, a resposta da API é retornada ao cliente da Web. Na próxima etapa, vamos mostrar como exibir o trajeto no mapa.

Exemplo de código completo

O código completo até este ponto está disponível no GitHub:step3_requestRoute

7. Mostrar o trajeto no mapa

Na etapa anterior, nos referimos a renderRoutes() quando recebemos a resposta do serviço Node.js. Agora vamos adicionar o código real para mostrar o trajeto no mapa.

No app.js, adicione o código abaixo:

let paths = [];
async function renderRoutes(data) {
  const { encoding } = await google.maps.importLibrary("geometry");
  let routes = data.routes;
  let decodedPaths = [];

  ///Display routes and markers
  routes.forEach((route,i) => {
      if(route.hasOwnProperty('polyline')){
        //Decode the encoded polyline
        decodedPaths.push(encoding.decodePath(route.polyline.encodedPolyline));

        //Draw polyline on the map
        for(let i = decodedPaths.length - 1; i >= 0; i--){
            let polyline = new google.maps.Polyline({
                map: map,
                path: decodedPaths[i],
                strokeColor: "#4285f4",
                strokeOpacity: 1,
                strokeWeight: 5
            });
            paths.push(polyline);
        }
        
        //Add markers for origin/destination
        addMarker(route.legs[0].startLocation.latLng,"A");
        addMarker(route.legs[0].endLocation.latLng,"B");
        //Set the viewport
        setViewport(route.viewport);
      } else {
        console.log("Route cannot be found");
      }
  });
}

A Routes API retorna polyline no formato encodedPolyline(padrão) ou geoJsonLinestring. Neste laboratório, usamos o formato encodedPolyline e o decodificamos usando a biblioteca de geometria do JavaScript do Maps.

Vamos usar o addMarker() para adicionar Marcadores Avançados de origem e destino. Em app.js, adicione o seguinte código:

let markers = [];
async function addMarker(pos,label){
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
  const { PinElement } = await google.maps.importLibrary("marker");
  const { LatLng } = await google.maps.importLibrary("core");
  let pinGlyph = new PinElement({
      glyphColor: "#fff",
      glyph: label
  });
  let marker = new AdvancedMarkerElement({
      position: new LatLng({lat:pos.latitude,lng:pos.longitude}),
      gmpDraggable: false,
      content: pinGlyph.element,
      map: map
  });
  markers.push(marker);
}

Aqui, criamos dois marcadores avançados: A para origem e B para destino. Saiba mais sobre os marcadores avançados.

Em seguida, vamos centralizar a janela de visualização do mapa na rota recuperada usando as informações convenientes de janela de visualização fornecidas pela API Routes. Em app.js, adicione o seguinte código:

async function setViewport(viewPort) {
  const { LatLng } = await google.maps.importLibrary("core");
  const { LatLngBounds } = await google.maps.importLibrary("core");
  let sw = new LatLng({lat:viewPort.low.latitude,lng:viewPort.low.longitude});
  let ne = new LatLng({lat:viewPort.high.latitude,lng:viewPort.high.longitude});
  map.fitBounds(new LatLngBounds(sw,ne));
}

Código de amostra completo O código completo até este ponto está disponível no GitHub:step4_displayRoute

8. Remover elementos do mapa

Aqui, queremos ir além. Vamos limpar o mapa antes de desenhar novos marcadores e rotas para evitar confusão.

No app.js, vamos adicionar mais uma função:

function clearUIElem(obj,type) {
  if(obj.length > 0){
      if(type == 'advMarker'){
          obj.forEach(function(item){
              item.map = null;
          });
      } else {
          obj.forEach(function(item){
              item.setMap(null);
          });
      }
  }
}

Adicione a seguinte linha no início de renderRoutes():

clearUIElem(paths,'polyline');

Adicione a seguinte linha no início de addMarker():

clearUIElem(markers,'advMarker');

Exemplo de código completo

O código completo até este ponto está disponível no GitHub:step5_removeElements

9. Parabéns

Você criou o objeto com sucesso.

O que você aprendeu

  • Ativar as APIs da Plataforma Google Maps
  • Carregar a API Google Maps JavaScript na página HTML
  • Importar a biblioteca Places da API Maps JavaScript
  • Vincular o serviço Place Autocomplete aos campos de texto
  • Solicitar uma rota usando a API Routes
  • Mostrar o trajeto em um mapa dinâmico
  • Criar um ID de mapa
  • Criar Marcadores Avançados

Saiba mais

Quais outros codelabs você quer ver?

Visualização de dados em mapas Como personalizar o estilo dos meus mapas Como criar interações 3D em mapas

O codelab que você quer ver não está listado acima? Solicite-o aqui.