Создайте планировщик маршрутов с помощью Place Autocomplete и Routes API

1. Обзор

Независимо от того, отправляетесь ли вы в путешествие, планируете ежедневные поездки на работу или проезжаете по шумному городу, добраться из пункта А в пункт Б — это больше, чем просто знать, куда вы хотите попасть. Надёжный инструмент для построения маршрута просто необходим.

С помощью платформы Google Maps вы можете добавить в свое приложение динамическую карту, позволить пользователям быстро вводить местоположения с помощью функции автозаполнения и отображать маршруты на карте.

В этом практическом занятии разработчики познакомятся с процессом создания веб-приложений с использованием Maps JavaScript API , Place Autocomplete и Routes API . Вы научитесь интегрировать несколько API платформы Google Карт с помощью настраиваемого руководства.

Что вы построите

В этом практическом занятии вы научитесь создавать веб-приложения с использованием HTML, CSS, JavaScript и бэкэнда Node.js.

Архитектура веб-приложения планировщика маршрутов

Веб-приложение для планирования маршрутов

Чему вы научитесь

  • Как включить API платформы Google Карт
  • Как интегрировать динамическую карту в веб-приложение
  • Как интегрировать сервис автозаполнения Places
  • Как запросить маршрут через Routes API
  • Как отобразить маршрут на динамической карте
  • Как создать идентификатор карты
  • Как добавить расширенные маркеры на динамическую карту

Что вам понадобится

Пример кода

Полное решение и пошаговый код доступны на GitHub . Код не содержит необходимых пакетов Node. Установите необходимые зависимости перед выполнением кода. Подробную информацию о необходимых пакетах можно найти в файле package.json (см. шаг 3).

2. Настройте проект и включите API.

Для включения вам потребуется включить Maps JavaScript API , Place Autocomplete и Routes API .

Настройте платформу Google Карт

Если у вас еще нет учетной записи Google Cloud Platform и проекта с включенным выставлением счетов, ознакомьтесь с руководством « Начало работы с Google Maps Platform», чтобы создать учетную запись для выставления счетов и проект.

  1. В Cloud Console щелкните раскрывающееся меню проектов и выберите проект, который вы хотите использовать для этой кодовой лаборатории. Выбрать проект
  2. Включите API платформы Google Карт, необходимые для этой лабораторной работы, на странице библиотеки API Карт . Для этого следуйте инструкциям в этом видео или в этой документации .
  3. Сгенерируйте ключ API на странице «Учётные данные» в Cloud Console. Вы можете следовать инструкциям в этом видео или в этой документации . Для всех запросов к платформе Google Карт требуется ключ API.

3. Настройте проект Node.js.

В этой лабораторной работе мы будем использовать Node.js для сбора данных об источнике и пункте назначения из Интернета и запроса маршрута через API маршрутов.

Предполагая, что вы уже установили Node.js , создайте каталог, который вы будете использовать для запуска этого проекта:

$ mkdir ac_routes
$ cd ac_routes

Инициализируйте новый пакет Node.js в каталоге вашего приложения:

$ npm init

Эта команда запросит у вас ряд данных, таких как имя и версия вашего приложения. Сейчас вы можете просто нажать клавишу RETURN, чтобы принять значения по умолчанию для большинства из них. Точкой входа по умолчанию является index.js, но вы можете изменить её на свой основной файл. В этой лабораторной работе основным файлом будет function/server.js (подробнее см. в шаге 6).

Кроме того, вы можете установить предпочитаемый вами фреймворк и модули. В этой лабораторной работе используются веб-фреймворк ( Express ) и парсер тела ( body-parser ). Подробнее см. в файле package.json .

4. Создайте динамическую карту

Теперь, когда у нас есть бэкэнд Node.js, давайте рассмотрим необходимые шаги для клиентской стороны.

  • Создайте HTML-страницу для приложения
  • Создайте CSS-файл для стилизации
  • Загрузите JavaScript API Google Карт на HTML-страницу
  • Вставьте свой ключ API в тег скрипта для аутентификации вашего приложения.
  • Создайте файл JavaScript для управления функциональностью приложения.

Создать HTML-страницу

  1. Создайте новый каталог в папке вашего проекта (в данном случае ac_routes)
     $ mkdir public
     $ cd public
    
  2. В публичном каталоге создайте index.html
  3. Скопируйте следующий код в 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>
    

Создать CSS-файл

  1. Создайте style.css в публичном каталоге
  2. Скопируйте следующий код в 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;}
    

API JavaScript для загрузки карт

В этой лабораторной работе мы будем использовать импорт динамической библиотеки для загрузки JavaScript API Карт. Подробнее см. здесь .

В файле index.html скопируйте следующий код перед закрывающим тегом body. Замените «YOUR_API_KEY» на свой ключ 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>

Создать файл JavaScript

  1. В публичном каталоге создайте app.js
  2. Скопируйте следующий код в 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();
     }());
    

DEMO_MAP_ID — это идентификатор, который можно использовать для примеров кода, требующих идентификатора карты. Этот идентификатор не предназначен для использования в рабочих приложениях и не может использоваться для функций, требующих облачной стилизации. В этой лабораторной работе нам потребуется идентификатор карты для расширенных маркеров на более позднем этапе. Подробнее о создании идентификатора карты для вашего приложения см. здесь.

В index.html добавьте ссылку на app.js перед закрывающимся тегом body и после тега script загрузки Maps JavaScript API.

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

Полный пример кода

Полный код на данный момент доступен на GitHub:step1_createDynamicMap

5. Введите адреса отправления и назначения.

  • Добавьте два текстовых поля в index.html для ввода исходного и конечного пункта.
  • Импорт библиотеки автозаполнения
  • Привязать службу автозаполнения к текстовым полям источника и назначения

Добавить текстовые поля

В index.html добавьте следующий код в качестве первого дочернего элемента div с классом 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>

Импорт и включение автозаполнения

Класс google.maps.places.Autocomplete — это виджет, предоставляющий подсказки мест на основе введенного пользователем текста. Он прикрепляется к элементу ввода типа «текст» и отслеживает ввод текста в этом поле. Список подсказок представлен в виде раскрывающегося списка и обновляется по мере ввода текста.

В app.js добавьте следующий код после инициализации карты:

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

После того, как пользователь выбрал место из списка подсказок автозаполнения, можно получить подробную информацию о нём с помощью метода getPlace() . Результат поиска места содержит множество информации о месте . В этой лабораторной работе мы будем использовать place_id для идентификации выбранного места. Идентификаторы мест однозначно идентифицируют место в базе данных Google Places и на Google Картах. Подробнее об идентификаторах мест можно узнать здесь.

Добавьте соответствующие стили

В style.css добавьте следующий код:

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

Полный пример кода

Полный код на данный момент доступен на GitHub:step2_inputAddress

6. Запросить маршрут

  • Добавьте кнопку «Получить маршрут» в index.html для инициирования запроса маршрута.
  • Эта кнопка запускает отправку данных об источнике и пункте назначения в службу Node.js.
  • Служба Node.js отправляет запрос к API Routes
  • Ответ API возвращается на сторону клиента для отображения.

Установив начальную и конечную точки маршрута и подготовив динамическую карту, пора проложить маршрут. На помощь приходит Routes API — оптимизированная по производительности версия сервиса маршрутизации и расстояний нового поколения. В этой лабораторной работе мы будем использовать Node.js для получения начальной и конечной точек маршрута из интернета и запроса маршрута через Routes API.

В index.html добавьте кнопку «Получить маршрут» перед закрывающим тегом div с классом aside :

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

В style.css добавьте следующую строку:

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

В app.js добавьте следующий код для отправки данных об источнике и пункте назначения в службу 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() будет использоваться для отрисовки маршрута на карте. Подробности мы рассмотрим на следующем шаге.

Создание сервера

В каталоге проекта (в данном случае ac_routes) создайте новую папку с именем function. Внутри неё создайте файл server.js. Этот файл служит точкой входа вашего проекта, которая настраивается при настройке проекта Node.js и обрабатывает три ключевые функции:

  1. Сбор данных из веб-клиента
  2. Отправка запросов к Routes API
  3. Возврат ответа API на сторону клиента

Скопируйте следующий код в файл server.js. Замените «YOUR_API_KEY» на свой ключ API. Для повышения безопасности ключа API настоятельно рекомендуется использовать отдельный ключ для бэкенда. См. раздел «Руководство по безопасности» .

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

Дополнительные сведения о получении маршрута с помощью Routes API см.

Запустить код

Запустите код ниже в командной строке:

$ node function/server.js

Откройте браузер и перейдите по адресу http://127.0.0.1:8080/index.html. Вы должны увидеть страницу приложения. До этого момента ответ API возвращался веб-клиенту. Давайте рассмотрим, как отобразить маршрут на карте, на следующем шаге.

Полный пример кода

Полный код на данный момент доступен на GitHub:step3_requestRoute

7. Отобразить маршрут на карте

На предыдущем шаге мы обращались к renderRoutes() после успешного получения ответа от сервиса Node.js. Теперь добавим код для отображения маршрута на карте.

В app.js добавьте следующий код:

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

API маршрутов возвращает полилинию в формате encodedPolyline (по умолчанию) или geoJsonLinestring. В этой лабораторной работе мы используем формат encodedPolyline и декодируем его с помощью библиотеки геометрии JavaScript Maps .

Мы будем использовать метод addMarker() для добавления расширенных маркеров начальной и конечной точек. В файл app.js добавьте следующий код:

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

Здесь мы создаём два расширенных маркера: A для пункта отправления и B для пункта назначения. Подробнее о расширенных маркерах читайте здесь.

Далее мы центрируем область просмотра карты по полученному маршруту, используя удобную информацию об области просмотра, предоставляемую API маршрутов. В файл app.js добавьте следующий код:

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

Полный пример кода. Полный код на данный момент доступен на GitHub:step4_displayRoute

8. Удалить элементы с карты

Здесь мы хотим сделать ещё один шаг. Давайте очистим карту, прежде чем рисовать новые маркеры и маршруты, чтобы избежать беспорядка.

В app.js добавим еще одну функцию:

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

Добавьте следующую строку в начало renderRoutes() :

clearUIElem(paths,'polyline');

Добавьте следующую строку в начало addMarker() :

clearUIElem(markers,'advMarker');

Полный пример кода

Полный код на данный момент доступен на GitHub:step5_removeElements

9. Поздравления

Вы успешно построили эту вещь.

Что вы узнали

  • Включить API платформы Google Карт
  • Загрузите JavaScript API Google Карт на HTML-страницу
  • Библиотека импорта мест, API JavaScript Карт
  • Привязать сервис Place Autocomplete к текстовым полям
  • Запросить маршрут через API маршрутов
  • Отобразить маршрут на динамической карте
  • Создать идентификатор карты
  • Создать расширенные маркеры

Узнать больше

Какие еще практические занятия вы хотели бы увидеть?

Визуализация данных на картах Подробнее о настройке стиля моих карт Создание 3D-взаимодействий на картах

Нужная вам кодлаб-программа не указана выше? Запросите её в новом выпуске здесь .