Crea un planificador de rutas con Place Autocomplete y la API de Routes

1. Descripción general

Ya sea que te embarques en un viaje por carretera, planifiques tu viaje diario o te desplaces por una ciudad bulliciosa, ir del punto A al punto B es más que saber a dónde quieres ir. Una herramienta confiable de generación de rutas es esencial.

Con Google Maps Platform, puedes agregar un mapa dinámico a tu aplicación, permitir que los usuarios ingresen ubicaciones rápidamente con la función de autocompletar y mostrar rutas en el mapa.

En este codelab, se guía a los desarrolladores para que creen una aplicación web con la API de Maps JavaScript, Place Autocomplete y la API de Routes. A través de un instructivo personalizable, aprenderás a integrar varias APIs de Google Maps Platform.

Qué compilarás

En este codelab, se te guiará para que compiles una aplicación web con HTML, CSS, JavaScript y un backend de Node.js.

Arquitectura de la app web del planificador de rutas

App web del planificador de rutas

Qué aprenderás

  • Cómo habilitar las APIs de Google Maps Platform
  • Cómo integrar un mapa dinámico en una aplicación web
  • Cómo integrar el servicio Place Autocomplete
  • Cómo solicitar una ruta a través de la API de Routes
  • Cómo mostrar la ruta en un mapa dinámico
  • Cómo crear un ID de mapa
  • Cómo agregar marcadores avanzados a un mapa dinámico

Requisitos

Código de muestra

La solución completa y el código paso a paso están disponibles en GitHub. El código no incluye los paquetes de Node necesarios. Instala las dependencias necesarias antes de ejecutar el código. Los detalles de los paquetes requeridos se pueden encontrar en el archivo package.json(explicado en el paso 3).

2. Configura el proyecto y habilita las APIs

Para el paso de habilitación, deberás habilitar la API de Maps JavaScript, Place Autocomplete y la API de Routes.

Configura Google Maps Platform

Si todavía no tienes una cuenta de Google Cloud Platform y un proyecto con la facturación habilitada, consulta la guía Cómo comenzar a utilizar Google Maps Platform para crear una cuenta de facturación y un proyecto.

  1. En Cloud Console, haz clic en el menú desplegable del proyecto y selecciona el proyecto que deseas usar para este codelab. Seleccionar proyecto
  2. Habilita las APIs de Google Maps Platform necesarias para este codelab en la página Biblioteca de la API de Maps. Para hacerlo, sigue los pasos que se indican en este video o en esta documentación.
  3. Genera una clave de API en la página Credenciales de Cloud Console. Puedes seguir los pasos que se indican en este video o en esta documentación. Todas las solicitudes a Google Maps Platform requieren una clave de API.

3. Configura un proyecto de Node.js

En este lab, usaremos Node.js para recopilar el origen y el destino de la Web y solicitar la ruta a través de la API de Routes.

Suponiendo que ya instalaste Node.js, crea un directorio que usarás para ejecutar este proyecto:

$ mkdir ac_routes
$ cd ac_routes

Inicializa un nuevo paquete de Node.js en el directorio de tu aplicación:

$ npm init

Este comando te solicitará varios datos, como el nombre y la versión de tu aplicación. Por ahora, puedes presionar RETORNO para aceptar los valores predeterminados de la mayoría de ellos. El punto de entrada predeterminado es index.js, pero puedes cambiarlo a tu archivo principal. En este lab, el archivo principal es function/server.js(más detalles en el paso 6).

Además, puedes instalar los módulos y el framework que prefieras. En este lab, se usan el framework web(Express) y el analizador de cuerpo(body-parser). Encontrarás más detalles en el archivo package.json.

4. Crea un mapa dinámico

Ahora que tenemos nuestro backend de Node.js, exploremos los pasos necesarios para el cliente.

  • Crea una página HTML para la aplicación
  • Crea un archivo CSS para aplicar estilo
  • Carga la API de Google Maps JavaScript en la página HTML
  • Pega tu clave de API en la etiqueta de secuencia de comandos para autenticar tu aplicación.
  • Crea un archivo JavaScript para controlar la funcionalidad de la aplicación

Crea una página HTML

  1. Crea un directorio nuevo en la carpeta de tu proyecto(ac_routes en este caso).
     $ mkdir public
     $ cd public
    
  2. En el directorio público, crea index.html
  3. Copia el siguiente código en 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>
    

Crea un archivo CSS

  1. Crea style.css en el directorio público
  2. Copia el siguiente código en 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;}
    

Carga la API de Maps JavaScript

En este lab, usaremos la importación dinámica de bibliotecas para cargar la API de Maps JavaScript. Obtén más información aquí.

En index.html, copia el siguiente código antes de la etiqueta de cierre del cuerpo. Reemplaza "YOUR_API_KEY" por tu propia clave 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>

Crea un archivo JavaScript

  1. En el directorio público, crea app.js
  2. Copia el siguiente código en 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();
     }());
    

El DEMO_MAP_ID es un ID que se puede usar para muestras de código que requieren un ID de Maps. Este ID no está diseñado para usarse en aplicaciones de producción y no se puede usar para funciones que requieren diseño basado en la nube. En este lab, se requiere un ID de mapa para los Marcadores avanzados en la etapa posterior. Obtén más detalles para crear un ID de mapa para tu aplicación.

En index.html, vincula app.js antes de la etiqueta de cierre del cuerpo y después de la etiqueta de secuencia de comandos de carga de la API de Maps JavaScript.

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

Código de muestra completo

El código completo hasta este punto está disponible en GitHub:step1_createDynamicMap

5. Ingresa las direcciones de origen y destino

  • Agrega dos campos de texto en index.html para la entrada de origen y destino
  • Importa la biblioteca de Autocomplete
  • Vincula el servicio de Autocomplete a los campos de texto de origen y destino

Agregar campos de texto

En index.html, agrega el siguiente código como el primer elemento secundario de div con la clase 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>

Importa y habilita Autocomplete

La clase google.maps.places.Autocomplete es un widget que proporciona predicciones de lugares basadas en la entrada de texto de un usuario. Se adjunta a un elemento de entrada de tipo texto y escucha la entrada de texto en ese campo. La lista de predicciones se presenta como una lista desplegable y se actualiza a medida que se ingresa texto.

En app.js, agrega el siguiente código después de la inicialización del 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();

Una vez que el usuario selecciona un lugar de la lista de predicciones de autocompletado, se puede recuperar un detalle del resultado del lugar con el método getPlace(). El resultado de lugar contiene mucha información sobre un lugar. En este lab, usaremos place_id para identificar el lugar seleccionado. Los IDs de lugar identifican de forma exclusiva un sitio en la base de datos de Google Places y en Google Maps. Obtén más detalles sobre los IDs de lugar.

Agrega estilos relevantes

En el archivo style.css, agrega el siguiente 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;}

Código de muestra completo

El código completo hasta este punto está disponible en GitHub:step2_inputAddress

6. Solicita la ruta

  • Agrega un botón "Obtener una ruta" a index.html para iniciar la solicitud de ruta.
  • Este botón activa el envío de datos de origen y destino al servicio de Node.js.
  • El servicio de Node.js envía una solicitud a la API de Routes
  • La respuesta de la API se devuelve al cliente para que se muestre.

Con el origen y el destino establecidos, y un mapa dinámico listo, es hora de obtener la ruta. La API de Routes, la versión de próxima generación y optimizada para el rendimiento del servicio de Directions y Distance Matrix, viene al rescate. En este lab, usaremos Node.js para recopilar el origen y el destino de la Web y solicitar la ruta a través de la API de Routes.

En index.html, agrega un botón “Obtener una ruta” antes de la etiqueta de cierre de div con la clase aside :

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

En el archivo style.css, agrega la siguiente línea:

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

En app.js, agrega el siguiente código para enviar datos de origen y destino al servicio de 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() es la función que usaremos para dibujar la ruta en el mapa. Veremos los detalles en el siguiente paso.

Crea el servidor

En el directorio del proyecto(ac_routes en este caso), crea una carpeta nueva llamada function. Dentro de esta carpeta, crea un archivo llamado server.js. El archivo actúa como el punto de entrada de tu proyecto, que se configura cuando configuras tu proyecto de Node.js, y controla tres funciones clave:

  1. Recopilación de datos del cliente web
  2. Envía solicitudes a la API de Routes
  3. Cómo devolver la respuesta de la API al cliente

Copia el siguiente código en server.js. Reemplaza "YOUR_API_KEY" por tu propia clave de API. Para mejorar la seguridad de la clave de API, te recomendamos que uses una clave distinta para el backend. Consulta la Guía de seguridad.

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

Obtén más detalles sobre la obtención de una ruta con la API de Routes.

Ejecuta el código

Ejecuta el siguiente código en la línea de comandos:

$ node function/server.js

Abre el navegador y visita http://127.0.0.1:8080/index.html. Deberías ver la página de la aplicación. Hasta esta etapa, la respuesta de la API se devuelve al cliente web. En el siguiente paso, veremos cómo mostrar la ruta en el mapa.

Código de muestra completo

El código completo hasta este punto está disponible en GitHub:step3_requestRoute

7. Cómo mostrar la ruta en el mapa

En el paso anterior, nos referimos a renderRoutes() cuando recibimos correctamente la respuesta del servicio de Node.js. Ahora agreguemos el código real para mostrar la ruta en el mapa.

En app.js, agrega el siguiente código:

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");
      }
  });
}

La API de Routes devuelve polyline en formato encodedPolyline(predeterminado) o geoJsonLinestring. En este lab, usamos el formato encodedPolyline y lo decodificamos con la biblioteca de geometría de Maps JavaScript.

Usaremos addMarker() para agregar marcadores avanzados para el origen y el destino. En app.js, agrega el siguiente 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);
}

Aquí creamos dos marcadores avanzados: A para el origen y B para el destino. Obtén más detalles sobre los marcadores avanzados.

A continuación, centraremos el viewport del mapa en la ruta recuperada con la información de viewport que proporciona la API de Routes. En app.js, agrega el siguiente 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 muestra completo El código completo hasta este punto está disponible en GitHub:step4_displayRoute

8. Cómo quitar elementos del mapa

Aquí queremos ir un paso más allá. Borremos el mapa antes de dibujar marcadores y rutas nuevos para evitar el desorden.

En app.js, agreguemos una función más:

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

Agrega la siguiente línea al comienzo de renderRoutes():

clearUIElem(paths,'polyline');

Agrega la siguiente línea al comienzo de addMarker():

clearUIElem(markers,'advMarker');

Código de muestra completo

El código completo hasta este punto está disponible en GitHub:step5_removeElements

9. Felicitaciones

Construiste correctamente el objeto.

Qué aprendiste

  • Habilita las APIs de Google Maps Platform
  • Carga la API de Google Maps JavaScript en la página HTML
  • Importa la biblioteca de Places de la API de Maps JavaScript
  • Vincula el servicio Place Autocomplete a los campos de texto
  • Cómo solicitar una ruta a través de la API de Routes
  • Cómo mostrar la ruta en un mapa dinámico
  • Crea un ID de mapa
  • Crea marcadores avanzados

Más información

¿Qué otros codelabs te gustaría ver?

Visualización de datos en mapas Más información para personalizar el diseño de mis mapas Cómo crear interacciones en 3D en mapas

¿El codelab que quieres no figura arriba? Crea un nuevo problema aquí para solicitarlo.