Crea un localizador de tiendas simple con Google Maps Platform (JavaScript)

1. Antes de comenzar

Una de las funciones más comunes de un sitio web es mostrar un mapa de Google que destaca una o más ubicaciones de una empresa, un establecimiento o alguna otra entidad con presencia física. La forma en que se implementan estos mapas puede variar considerablemente según los requisitos, como la cantidad de ubicaciones y la frecuencia con la que cambian.

En este codelab, se presenta el caso de uso más sencillo: una pequeña cantidad de ubicaciones que rara vez cambian, como un localizador de tiendas para una empresa con una cadena de tiendas. En este caso, puedes usar un enfoque de tecnología relativamente baja sin programación del servidor. Pero eso no significa que no puedas ser creativo, así que aprovecha el formato de datos GeoJSON para almacenar y renderizar información arbitraria sobre cada tienda de tu mapa, así como personalizar los marcadores y el estilo general del mapa en sí.

Por último, como beneficio adicional, puedes usar Cloud Shell para desarrollar y alojar el localizador de tiendas. Aunque esta herramienta no es estrictamente obligatoria, te permite utilizar cualquier dispositivo que ejecute un navegador web para desarrollar el localizador de tiendas y ponerlo a disposición del público en línea.

489628918395c3d0.png

Requisitos previos

  • Conocimientos básicos de HTML y JavaScript

Actividades

  • Mostrarás un mapa con un conjunto de ubicaciones de las tiendas y con información almacenada en formato GeoJSON.
  • Personalizarás los marcadores y el mapa en sí.
  • Mostrarás información adicional sobre la tienda cuando se hace clic en su marcador.
  • Agregarás una barra de búsqueda de Place Autocomplete a la página web.
  • Identificarás la ubicación de la tienda más cercana a un punto de partida proporcionado por el usuario.

2. Prepárate

En el paso 3 de la sección siguiente, habilita estas tres API para este codelab:

  • API de Maps JavaScript
  • API de Places
  • API de Distance Matrix

Comienza a utilizar Google Maps Platform

Si nunca usaste Google Maps Platform, sigue la guía Cómo comenzar a utilizar Google Maps Platform o mira la lista de reproducción Cómo comenzar a utilizar Google Maps Platform para completar los siguientes pasos:

  1. Crear una cuenta de facturación
  2. Crear un proyecto
  3. Habilitar las API y los SDK de Google Maps Platform (enumerados en la sección anterior)
  4. Generar una clave de API

Activa Cloud Shell

En este codelab, usarás Cloud Shell, un entorno de línea de comandos que se ejecuta en Google Cloud y que proporciona acceso a productos y recursos que también se ejecutan en Google Cloud. Eso te permite alojar y ejecutar tu proyecto completamente en tu navegador web.

Para activar Cloud Shell en Cloud Console, haz clic en Activar Cloud Shell 89665d8d348105cd.png (el aprovisionamiento y la conexión al entorno debería llevar solo unos minutos).

5f504766b9b3be17.png

Esto abrirá una nueva shell en la parte inferior del navegador después de mostrar un anuncio intersticial introductorio.

d3bb67d514893d1f.png

Una vez conectado a Cloud Shell, deberías ver que ya estás autenticado y que el proyecto ya tiene asignado el ID que seleccionaste durante la configuración.

$ gcloud auth list
Credentialed Accounts:
ACTIVE  ACCOUNT
  *     <myaccount>@<mydomain>.com
$ gcloud config list project
[core]
project = <YOUR_PROJECT_ID>

Si, por algún motivo, el proyecto no se configuró, ejecuta el siguiente comando:

$ gcloud config set project <YOUR_PROJECT_ID>

3. "Hola, mundo" con un mapa

Comienza a desarrollar con un mapa

En Cloud Shell, primero creas una página HTML que servirá como base para el resto del codelab.

  1. En la barra de herramientas de Cloud Shell, haz clic en Iniciar editor 996514928389de40.png para abrir un editor de código en una pestaña nueva.

Este editor de código basado en la Web le permite editar fácilmente archivos en Cloud Shell.

Screen Shot 2017-04-19 at 10.22.48 AM.png

  1. Haz clic en Archivo > Carpeta nueva a fin de crear un nuevo directorio store-locator para tu app en el editor de código.

NewFolder.png

  1. Asigna el nombre store-locator a la carpeta nueva.

Ahora crearás una página web con un mapa.

  1. Crea un archivo en el directorio store-locator llamado index.html.

3c257603da5ab524.png

  1. Ingresa el siguiente contenido en el archivo index.html:

index.html

<html>

<head>
    <title>Store Locator</title>
    <style>
        #map {
            height: 100%;
        }

        html,
        body {
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <!-- The div to hold the map -->
    <div id="map"></div>

    <script src="app.js"></script>
    <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a">
    </script>
</body>

</html>

En esta página HTML, puede verse el mapa. Se le aplicaron algunas propiedades de CSS para garantizar que el mapa ocupe toda la página, tiene una etiqueta <div> para retener el mapa y un par de etiquetas <script>. La primera etiqueta "script" carga un archivo JavaScript llamado app.js, que contiene todo el código de JavaScript. La segunda etiqueta "script" carga la clave de API, incluye el uso de la biblioteca de Places para la funcionalidad de autocompletado que agregarás más adelante y especifica el nombre de la función de JavaScript que se ejecuta una vez que se carga la API de Maps JavaScript, es decir, initMap.

  1. Reemplaza el texto YOUR_API_KEY en el fragmento de código por la clave de API que generaste antes en este codelab.
  2. Por último, crea otro archivo llamado app.js con el siguiente código:

app.js

function initMap() {
   // Create the map.
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 52.632469, lng: -1.689423 },
    });

}

Ese es el código mínimo que se requiere para crear un mapa. Pásale una referencia a la etiqueta <div> para que retenga el mapa y especifica el centro y el nivel de zoom.

Para probar esta app, puedes ejecutar el servidor HTTP simple de Python en Cloud Shell.

  1. Ve a Cloud Shell y escribe lo siguiente:
$ cd store-locator
$ python -m SimpleHTTPServer 8080

Verás algunas líneas de resultados de registro que indican que, efectivamente, estás ejecutando el servidor HTTP simple en Cloud Shell y que la aplicación web está escuchando el puerto localhost 8080.

  1. Haz clic en Vista previa en la Web 95e419ae763a1d48.png en la barra de herramientas de Cloud Console para abrir una pestaña del navegador web y selecciona Vista previa en el puerto 8080.

47b06e5169eb5add.png

bdab1f021a3b91d5.png

Al hacer clic en este elemento de menú, se abre una pestaña nueva en el navegador web con el contenido del HTML que se entrega desde el servidor HTTP simple de Python. Si todo salió bien, deberías ver un mapa centrado en Londres, Inglaterra.

Para detener el servidor HTTP simple, presiona Control+C en Cloud Shell.

4. Propaga el mapa con el contenido de GeoJSON

Ahora, observa los datos de las tiendas. GeoJSON es un formato de datos que representa accidentes geográficos simples, como puntos, líneas o polígonos en un mapa. Los accidentes también pueden contener datos arbitrarios. Esto hace que GeoJSON sea un excelente candidato para representar las tiendas, que, básicamente, son puntos en un mapa con algunos datos adicionales, como el nombre, el horario de atención y el número de teléfono de la tienda. Lo que es más importante, GeoJSON tiene excelente compatibilidad con Google Maps, lo que significa que puedes enviar un documento GeoJSON a un mapa de Google y lo renderizará correctamente.

  1. Crea un archivo nuevo llamado stores.json y pega el siguiente código:

stores.json

{
    "type": "FeatureCollection",
    "features": [{
            "geometry": {
                "type": "Point",
                "coordinates": [-0.1428115,
                    51.5125168
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Modern twists on classic pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Mayfair",
                "phone": "+44 20 1234 5678",
                "storeid": "01"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-2.579623,
                    51.452251
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Come and try our award-winning cakes and pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Bristol",
                "phone": "+44 117 121 2121",
                "storeid": "02"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [
                    1.273459,
                    52.638072
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Whatever the occasion, whether it's a birthday or a wedding, Josie's Patisserie has the perfect treat for you. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Norwich",
                "phone": "+44 1603 123456",
                "storeid": "03"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-1.9912838,
                    50.8000418
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "A gourmet patisserie that will delight your senses. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Wimborne",
                "phone": "+44 1202 343434",
                "storeid": "04"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-2.985933,
                    53.408899
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Spoil yourself or someone special with our classic pastries. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Liverpool",
                "phone": "+44 151 444 4444",
                "storeid": "05"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-1.689423,
                    52.632469
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Come and feast your eyes and tastebuds on our delicious pastries and cakes. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Tamworth",
                "phone": "+44 5555 55555",
                "storeid": "06"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-3.155305,
                    51.479756
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "patisserie",
                "hours": "10am - 6pm",
                "description": "Josie's Patisserie is family-owned, and our delectable pastries, cakes, and great coffee are renowed. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Patisserie Cardiff",
                "phone": "+44 29 6666 6666",
                "storeid": "07"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-0.725019,
                    52.668891
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Oakham's favorite spot for fresh coffee and delicious cakes. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Oakham",
                "phone": "+44 7777 777777",
                "storeid": "08"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-2.477653,
                    53.735405
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Enjoy freshly brewed coffe, and home baked cakes in our homely cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Blackburn",
                "phone": "+44 8888 88888",
                "storeid": "09"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-0.211363,
                    51.108966
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "A delicious array of pastries with many flavours, and fresh coffee in an snug cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Crawley",
                "phone": "+44 1010 101010",
                "storeid": "10"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-0.123559,
                    50.832679
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Grab a freshly brewed coffee, a decadent cake and relax in our idyllic cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Brighton",
                "phone": "+44 1313 131313",
                "storeid": "11"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [-3.319575,
                    52.517827
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Come in and unwind at this idyllic cafe with fresh coffee and home made cakes. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Newtown",
                "phone": "+44 1414 141414",
                "storeid": "12"
            }
        },
        {
            "geometry": {
                "type": "Point",
                "coordinates": [
                    1.158167,
                    52.071634
                ]
            },
            "type": "Feature",
            "properties": {
                "category": "cafe",
                "hours": "8am - 9:30pm",
                "description": "Fresh coffee and delicious cakes in an snug cafe. We're part of a larger chain of patisseries and cafes.",
                "name": "Josie's Cafe Ipswich",
                "phone": "+44 1717 17171",
                "storeid": "13"
            }
        }
    ]
}

Aunque son muchos datos, si los observas detenidamente, verás que se trata de la misma estructura repetida para cada tienda. Cada tienda se representa como un objeto Point de GeoJSON junto con sus coordenadas y los datos adicionales que se pasan con la clave properties. Es interesante mencionar que GeoJSON permite incluir claves con nombres arbitrarios dentro de la clave properties. En este codelab, esas claves son category, hours, description, name y phone.

  1. Ahora, edita app.js para que cargue el GeoJSON de stores.js en tu mapa.

app.js

function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
  });

  // Load the stores GeoJSON onto the map.
  map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});

  const apiKey = 'YOUR_API_KEY';
  const infoWindow = new google.maps.InfoWindow();

  // Show the information for a store when its marker is clicked.
  map.data.addListener('click', (event) => {
    const category = event.feature.getProperty('category');
    const name = event.feature.getProperty('name');
    const description = event.feature.getProperty('description');
    const hours = event.feature.getProperty('hours');
    const phone = event.feature.getProperty('phone');
    const position = event.feature.getGeometry().get();
    const content = `
      <h2>${name}</h2><p>${description}</p>
      <p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
    `;

    infoWindow.setContent(content);
    infoWindow.setPosition(position);
    infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
    infoWindow.open(map);
  });
}

En el ejemplo de código, llamaste a loadGeoJson y le pasaste el nombre del archivo JSON para cargar el contenido GeoJSON. También definiste una función que se ejecuta cada vez que se hace clic en un marcador. Luego, la función puede acceder a los datos adicionales de la tienda en cuyo marcador se hizo clic y usar ese contenido en una ventana de información que se muestra. Para probar esta app, puedes ejecutar el servidor HTTP simple de Python con el mismo comando que antes.

  1. Vuelve a Cloud Shell y escribe lo siguiente:
$ python -m SimpleHTTPServer 8080
  1. Vuelve a hacer clic en Vista previa en la Web 95e419ae763a1d48.png > Vista previa en el puerto 8080. Deberías ver un mapa lleno de marcadores en los que puedes hacer clic para ver los detalles de cada tienda, como se muestra en el ejemplo siguiente. ¡Excelente!

c4507f7d3ea18439.png

5. Personaliza el mapa

Ya casi terminas. Tienes un mapa con todos los marcadores de tiendas que muestran información adicional cuando se hace clic en ellos. Pero se ve igual que cualquier otro mapa de Google. ¡Qué aburrido! Puedes usar un estilo de mapa personalizado, marcadores, logotipos e imágenes de Street View para darle un toque diferente.

Aquí puedes ver una nueva versión de app.js, a la que se le agregó un estilo personalizado:

app.js

const mapStyle = [{
  'featureType': 'administrative',
  'elementType': 'all',
  'stylers': [{
    'visibility': 'on',
  },
  {
    'lightness': 33,
  },
  ],
},
{
  'featureType': 'landscape',
  'elementType': 'all',
  'stylers': [{
    'color': '#f2e5d4',
  }],
},
{
  'featureType': 'poi.park',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#c5dac6',
  }],
},
{
  'featureType': 'poi.park',
  'elementType': 'labels',
  'stylers': [{
    'visibility': 'on',
  },
  {
    'lightness': 20,
  },
  ],
},
{
  'featureType': 'road',
  'elementType': 'all',
  'stylers': [{
    'lightness': 20,
  }],
},
{
  'featureType': 'road.highway',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#c5c6c6',
  }],
},
{
  'featureType': 'road.arterial',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#e4d7c6',
  }],
},
{
  'featureType': 'road.local',
  'elementType': 'geometry',
  'stylers': [{
    'color': '#fbfaf7',
  }],
},
{
  'featureType': 'water',
  'elementType': 'all',
  'stylers': [{
    'visibility': 'on',
  },
  {
    'color': '#acbcc9',
  },
  ],
},
];

function initMap() {
  // Create the map.
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 7,
    center: {lat: 52.632469, lng: -1.689423},
    styles: mapStyle,
  });

  // Load the stores GeoJSON onto the map.
  map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});

  // Define the custom marker icons, using the store's "category".
  map.data.setStyle((feature) => {
    return {
      icon: {
        url: `img/icon_${feature.getProperty('category')}.png`,
        scaledSize: new google.maps.Size(64, 64),
      },
    };
  });

  const apiKey = 'YOUR_API_KEY';
  const infoWindow = new google.maps.InfoWindow();

  // Show the information for a store when its marker is clicked.
  map.data.addListener('click', (event) => {
    const category = event.feature.getProperty('category');
    const name = event.feature.getProperty('name');
    const description = event.feature.getProperty('description');
    const hours = event.feature.getProperty('hours');
    const phone = event.feature.getProperty('phone');
    const position = event.feature.getGeometry().get();
    const content = `
      <img style="float:left; width:200px; margin-top:30px" src="img/logo_${category}.png">
      <div style="margin-left:220px; margin-bottom:20px;">
        <h2>${name}</h2><p>${description}</p>
        <p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
        <p><img src="https://maps.googleapis.com/maps/api/streetview?size=350x120&location=${position.lat()},${position.lng()}&key=${apiKey}&solution_channel=GMP_codelabs_simplestorelocator_v1_a"></p>
      </div>
      `;

    infoWindow.setContent(content);
    infoWindow.setPosition(position);
    infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
    infoWindow.open(map);
  });

}

Esto es lo que agregaste:

  • La variable mapStyle contiene toda la información para aplicarle estilos al mapa. (Como beneficio adicional, incluso puedes crear tu propio estilo si lo deseas).
  • Utilizaste el método map.data.setStyle para aplicar marcadores personalizados, uno diferente para cada category del GeoJSON.
  • Modificaste la variable content para incluir un logotipo (nuevamente utilizado la category de GeoJSON) y una imagen de Street View para la ubicación de la tienda.

Antes de implementar este contenido, debes completar algunos pasos:

  1. Reemplaza la string 'YOUR_API_KEY' en app.js por tu propia clave de API, que ya usaste antes (la misma que pegaste en index.html), dejando las comillas intactas. De este modo, estableces el valor correcto para la variable apiKey.
  2. Ejecuta los siguientes comandos en Cloud Shell para descargar los gráficos de los marcadores y logotipos. Asegúrate de estar en el directorio store-locator. Usa Control+C para detener el servidor HTTP simple si se está ejecutando.
$ mkdir -p img; cd img
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/icon_cafe.png
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/icon_patisserie.png
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/logo_cafe.png
$ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/logo_patisserie.png
  1. Ejecuta el comando siguiente para obtener una vista previa del localizador de tiendas terminado:
$ python -m SimpleHTTPServer 8080

Cuando vuelvas a cargar la vista previa, deberías ver un mapa como este con estilo personalizado, imágenes de marcadores personalizadas, un mejor formato de la ventana de información y una imagen de Street View para cada ubicación:

3d8d13da126021dd.png

6. Obtén la entrada del usuario

Por lo general, los usuarios de localizadores de tiendas desean saber qué tienda está más cerca de ellos o de una dirección desde la que planean comenzar su recorrido. Agrega una barra de búsqueda de Place Autocomplete para permitir que el usuario ingrese fácilmente una dirección inicial. Place Autocomplete proporciona una funcionalidad de autocompletado similar a la que tienen otras barras de búsqueda de Google, pero las predicciones son todos lugares de la Google Maps Platform.

  1. Vuelve a editar index.html para modificar el estilo de la barra de búsqueda con autocompletado y el panel lateral asociado de resultados. No olvides reemplazar tu clave de API si pegaste el código anterior.

index.html

<html>

<head>
  <title>Store Locator</title>
  <style>
    #map {
      height: 100%;
    }

    html,
    body {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    /* Styling for Autocomplete search bar */
    #pac-card {
      background-color: #fff;
      border-radius: 2px 0 0 2px;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
      box-sizing: border-box;
      font-family: Roboto;
      margin: 10px 10px 0 0;
      -moz-box-sizing: border-box;
      outline: none;
    }

    #pac-container {
      padding-top: 12px;
      padding-bottom: 12px;
      margin-right: 12px;
    }

    #pac-input {
      background-color: #fff;
      font-family: Roboto;
      font-size: 15px;
      font-weight: 300;
      margin-left: 12px;
      padding: 0 11px 0 13px;
      text-overflow: ellipsis;
      width: 400px;
    }

    #pac-input:focus {
      border-color: #4d90fe;
    }

    #title {
      color: #fff;
      background-color: #acbcc9;
      font-size: 18px;
      font-weight: 400;
      padding: 6px 12px;
    }

    .hidden {
      display: none;
    }

    /* Styling for an info pane that slides out from the left.
     * Hidden by default. */
    #panel {
      height: 100%;
      width: null;
      background-color: white;
      position: fixed;
      z-index: 1;
      overflow-x: hidden;
      transition: all .2s ease-out;
    }

    .open {
      width: 250px;
    }

    .place {
      font-family: 'open sans', arial, sans-serif;
      font-size: 1.2em;
      font-weight: 500;
      margin-block-end: 0px;
      padding-left: 18px;
      padding-right: 18px;
    }

    .distanceText {
      color: silver;
      font-family: 'open sans', arial, sans-serif;
      font-size: 1em;
      font-weight: 400;
      margin-block-start: 0.25em;
      padding-left: 18px;
      padding-right: 18px;
    }
  </style>
</head>

<body>
  <!-- The div to hold the map -->
  <div id="map"></div>

  <script src="app.js"></script>
  <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a">
  </script>
</body>

</html>

La barra de búsqueda con autocompletado y el panel deslizable están inicialmente ocultos y aparecen cuando se los necesita.

  1. Ahora, agrega el widget de autocompletado al mapa al final de la función initMap en app.js, justo antes de la llave de cierre.

app.js

  // Build and add the search bar
  const card = document.createElement('div');
  const titleBar = document.createElement('div');
  const title = document.createElement('div');
  const container = document.createElement('div');
  const input = document.createElement('input');
  const options = {
    types: ['address'],
    componentRestrictions: {country: 'gb'},
  };

  card.setAttribute('id', 'pac-card');
  title.setAttribute('id', 'title');
  title.textContent = 'Find the nearest store';
  titleBar.appendChild(title);
  container.setAttribute('id', 'pac-container');
  input.setAttribute('id', 'pac-input');
  input.setAttribute('type', 'text');
  input.setAttribute('placeholder', 'Enter an address');
  container.appendChild(input);
  card.appendChild(titleBar);
  card.appendChild(container);
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);

  // Make the search bar into a Places Autocomplete search bar and select
  // which detail fields should be returned about the place that
  // the user selects from the suggestions.
  const autocomplete = new google.maps.places.Autocomplete(input, options);

  autocomplete.setFields(
      ['address_components', 'geometry', 'name']);

El código restringe las sugerencias de autocompletado para que solo se muestren las direcciones (dado que Place Autocomplete también puede encontrar coincidencias con nombres de establecimientos y ubicaciones administrativas), y limita las direcciones que se muestran solo a las del Reino Unido. Agregar estas especificaciones opcionales reducirá la cantidad de caracteres que el usuario deba ingresar a fin de limitar las predicciones para que se muestre la dirección que busca. Luego, hace que el elemento div de autocompletado que creaste se mueva a la esquina superior derecha del mapa y especifica los campos que deben mostrarse sobre cada sitio que aparezca en la respuesta.

  1. Ejecuta el comando siguiente para reiniciar el servidor y actualizar la vista previa:
$ python -m SimpleHTTPServer 8080

Deberías ver un widget de autocompletado en la esquina superior derecha del mapa con las direcciones del Reino Unido que coinciden con lo que escribes.

5163f34a03910187.png

Ahora, debes agregar un mecanismo que controle cuando el usuario selecciona una predicción del widget de autocompletado y usarla como base para calcular las distancias a tus tiendas.

  1. Agrega el siguiente código al final de initMap en app.js, después del código que acabas de pegar.

app.js

 // Set the origin point when the user selects an address
  const originMarker = new google.maps.Marker({map: map});
  originMarker.setVisible(false);
  let originLocation = map.getCenter();

  autocomplete.addListener('place_changed', async () => {
    originMarker.setVisible(false);
    originLocation = map.getCenter();
    const place = autocomplete.getPlace();

    if (!place.geometry) {
      // User entered the name of a Place that was not suggested and
      // pressed the Enter key, or the Place Details request failed.
      window.alert('No address available for input: \'' + place.name + '\'');
      return;
    }

    // Recenter the map to the selected address
    originLocation = place.geometry.location;
    map.setCenter(originLocation);
    map.setZoom(9);
    console.log(place);

    originMarker.setPosition(originLocation);
    originMarker.setVisible(true);

    // Use the selected address as the origin to calculate distances
    // to each of the store locations
    const rankedStores = await calculateDistances(map.data, originLocation);
    showStoresList(map.data, rankedStores);

    return;
  });

El código agrega un objeto de escucha para que, cuando el usuario hace clic en una de las sugerencias, el mapa se centre en la dirección seleccionada y se establezca ese origen como base para calcular las distancias. Debes implementar los cálculos de distancia en el próximo paso.

7. Muestra las tiendas más cercanas

La API de Directions funciona de manera muy similar a lo que sucede cuando se solicitan instrucciones sobre cómo llegar en la app de Google Maps, es decir, se debe ingresar un origen y un destino para recibir una ruta entre ambos. La API de Distance Matrix amplía este concepto y permite identificar las combinaciones óptimas entre varios orígenes posibles y múltiples destinos posibles según los tiempos de viaje y la distancia. En este caso, para ayudar a que el usuario encuentre la tienda más cercana a la dirección seleccionada, debes proporcionar un origen y un array de ubicaciones de las tiendas como destinos.

  1. Agrega una nueva función a app.js llamada calculateDistances.

app.js

async function calculateDistances(data, origin) {
  const stores = [];
  const destinations = [];

  // Build parallel arrays for the store IDs and destinations
  data.forEach((store) => {
    const storeNum = store.getProperty('storeid');
    const storeLoc = store.getGeometry().get();

    stores.push(storeNum);
    destinations.push(storeLoc);
  });

  // Retrieve the distances of each store from the origin
  // The returned list will be in the same order as the destinations list
  const service = new google.maps.DistanceMatrixService();
  const getDistanceMatrix =
    (service, parameters) => new Promise((resolve, reject) => {
      service.getDistanceMatrix(parameters, (response, status) => {
        if (status != google.maps.DistanceMatrixStatus.OK) {
          reject(response);
        } else {
          const distances = [];
          const results = response.rows[0].elements;
          for (let j = 0; j < results.length; j++) {
            const element = results[j];
            const distanceText = element.distance.text;
            const distanceVal = element.distance.value;
            const distanceObject = {
              storeid: stores[j],
              distanceText: distanceText,
              distanceVal: distanceVal,
            };
            distances.push(distanceObject);
          }

          resolve(distances);
        }
      });
    });

  const distancesList = await getDistanceMatrix(service, {
    origins: [origin],
    destinations: destinations,
    travelMode: 'DRIVING',
    unitSystem: google.maps.UnitSystem.METRIC,
  });

  distancesList.sort((first, second) => {
    return first.distanceVal - second.distanceVal;
  });

  return distancesList;
}

La función llama a la API de Distance Matrix utilizando el origen que se le pasó como un único origen y las ubicaciones de las tiendas como un array de destinos. Con esa información, crea un array de objetos que almacena el ID de la tienda, la distancia expresada en una string escrita en lenguaje natural y la distancia expresada en metros como un valor numérico. Luego, ordena el array.

El usuario espera ver una lista de las tiendas ordenadas en función de su distancia: de la más cercana a la más lejana. Utiliza la lista que devuelve la función calculateDistances para conocer el orden en que deben mostrarse las tiendas a fin de propagar la ficha del panel lateral con los datos de cada tienda.

  1. Agrega una nueva función a app.js llamada showStoresList.

app.js

function showStoresList(data, stores) {
  if (stores.length == 0) {
    console.log('empty stores');
    return;
  }

  let panel = document.createElement('div');
  // If the panel already exists, use it. Else, create it and add to the page.
  if (document.getElementById('panel')) {
    panel = document.getElementById('panel');
    // If panel is already open, close it
    if (panel.classList.contains('open')) {
      panel.classList.remove('open');
    }
  } else {
    panel.setAttribute('id', 'panel');
    const body = document.body;
    body.insertBefore(panel, body.childNodes[0]);
  }

  // Clear the previous details
  while (panel.lastChild) {
    panel.removeChild(panel.lastChild);
  }

  stores.forEach((store) => {
    // Add store details with text formatting
    const name = document.createElement('p');
    name.classList.add('place');
    const currentStore = data.getFeatureById(store.storeid);
    name.textContent = currentStore.getProperty('name');
    panel.appendChild(name);
    const distanceText = document.createElement('p');
    distanceText.classList.add('distanceText');
    distanceText.textContent = store.distanceText;
    panel.appendChild(distanceText);
  });

  // Open the panel
  panel.classList.add('open');

  return;
}
  1. Ejecuta el comando siguiente para reiniciar el servidor y actualizar la vista previa.
$ python -m SimpleHTTPServer 8080
  1. Por último, escribe una dirección del Reino Unido en la barra de búsqueda con autocompletado y haz clic en una de las sugerencias.

El mapa debe centrarse en esa dirección, y debe aparecer una barra lateral que muestre las ubicaciones de las tiendas en función de la distancia hasta la dirección seleccionada. En la siguiente imagen, puede verse un ejemplo:

489628918395c3d0.png

8. Opcional: Aloja tu página web

Hasta este punto, solo podrás ver tu mapa cuando estés ejecutando de forma activa tu servidor HTTP de Python. A fin de ver el mapa más allá de tu sesión activa de Cloud Shell o compartir la URL del mapa con otras personas, utiliza Cloud Storage para alojar tu página web. Cloud Storage es un servicio web de almacenamiento en archivos en línea que permite guardar datos y acceder a ellos utilizando la infraestructura de Google. El servicio combina el rendimiento y la escalabilidad de Google Cloud con capacidades avanzadas de uso compartido y seguridad. También ofrece un nivel gratuito, que es ideal para alojar un localizador de tiendas simple como el que estás creando.

Con Cloud Storage, los archivos se almacenan en buckets, que son similares a los directorios de la computadora. Para alojar tu página web, primero debes crear un bucket. Debes elegir un nombre único para tu bucket. Por ejemplo, puedes usar tu nombre como parte del nombre del bucket.

  1. Una vez que elijas un nombre, ejecuta el siguiente comando en Cloud Shell:
$ gsutil mb gs://yourname-store-locator

gsutil es la herramienta para interactuar con Cloud Storage. El comando mb se utiliza para crear un bucket (y proviene de las palabras en inglés "make bucket"). Para obtener más información sobre todos los comandos disponibles, incluidos los que usas, consulta la herramienta gsutil.

De forma predeterminada, los buckets y archivos que alojes en Cloud Storage son privados. No obstante, para el localizador de tiendas, todos los archivos deberían ser públicos a fin de que sean accesibles para todos en Internet. Podrías convertir cada archivo en público después de subirlo, pero sería tedioso. En cambio, puedes simplemente configurar el nivel de acceso predeterminado para el bucket que creaste y todos los archivos que subas lo heredarán.

  1. Ejecuta el siguiente comando y reemplaza yourname-store-locator por el nombre que elegiste para tu bucket:
$ gsutil defacl ch -u AllUsers:R gs://yourname-store-locator
  1. Ahora puedes subir todos tus archivos del directorio actual (por ahora, solo tus archivos index.html y app.js) con el siguiente comando:
$ gsutil -h "Cache-Control:no-cache" cp * gs://yourname-store-locator

Ahora, deberías tener una página web con un mapa en línea. La URL para visualizarla será http://storage.googleapis.com/yourname-store-locator/index.html, con el nombre de bucket que elegiste anteriormente en lugar de yourname-store-locator.

Haz limpieza

La manera más fácil de limpiar todos los recursos creados en este proyecto es cerrar el proyecto de Google Cloud que creaste al comienzo de este instructivo:

  • Abre la página Configuración de Cloud Console.
  • Haz clic en Seleccionar un proyecto.
  • Selecciona el proyecto que creaste al comienzo de este instructivo y haz clic en Abrir.
  • Ingresa el ID del proyecto y haz clic en Cerrar.

9. Felicitaciones

¡Felicitaciones! Completaste este codelab.

Lo que aprendiste

Más información

¿Qué otros codelabs te gustaría ver?

Visualización de datos en mapas Más información sobre cómo personalizar el estilo de mis mapas Creación de interacciones 3D en los mapas

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

Si deseas ver el código con más detalle, consulta el repositorio de código fuente en https://github.com/googlecodelabs/google-maps-simple-store-locator.