Xây dựng công cụ lập kế hoạch đường đi bằng tính năng Tự động hoàn thành địa điểm và Routes API

1. Tổng quan

Dù bạn đang bắt đầu một chuyến đi đường dài, lên kế hoạch cho chuyến đi làm hằng ngày hay lái xe trong một thành phố nhộn nhịp, thì việc di chuyển từ điểm A đến điểm B không chỉ đơn thuần là biết nơi bạn muốn đến. Một công cụ tạo tuyến đường đáng tin cậy là điều cần thiết.

Với Nền tảng Google Maps, bạn có thể thêm một bản đồ động vào ứng dụng của mình, cho phép người dùng nhanh chóng nhập vị trí bằng tính năng tự động hoàn thành và hiển thị các tuyến đường trên bản đồ.

Lớp học lập trình này hướng dẫn nhà phát triển cách tạo một ứng dụng web bằng Maps JavaScript API, tính năng Tự động hoàn thành địa điểmRoutes API. Bạn sẽ tìm hiểu cách tích hợp nhiều API của Nền tảng Google Maps thông qua một hướng dẫn có thể tuỳ chỉnh.

Sản phẩm bạn sẽ tạo ra

Lớp học lập trình này sẽ hướng dẫn bạn cách tạo một ứng dụng web bằng HTML, CSS, JavaScript và một phần phụ trợ Node.js.

Cấu trúc ứng dụng web lập kế hoạch tuyến đường

Ứng dụng web lập kế hoạch tuyến đường

Kiến thức bạn sẽ học được

  • Cách bật API Nền tảng Google Maps
  • Cách tích hợp bản đồ động vào một ứng dụng web
  • Cách tích hợp dịch vụ Tự động hoàn thành địa điểm
  • Cách yêu cầu một tuyến đường thông qua Routes API
  • Cách hiển thị tuyến đường trên bản đồ động
  • Cách tạo mã bản đồ
  • Cách thêm điểm đánh dấu nâng cao vào bản đồ động

Bạn cần có

Mã mẫu

Bạn có thể xem toàn bộ giải pháp và mã từng bước trên GitHub. Mã này không bao gồm các gói Node bắt buộc. Cài đặt các phần phụ thuộc cần thiết trước khi thực thi mã. Bạn có thể xem thông tin chi tiết về các gói bắt buộc trong tệp package.json(được giải thích trong Bước 3).

2. Thiết lập dự án và bật API

Đối với bước bật, bạn sẽ cần bật Maps JavaScript API, Tính năng tự động hoàn thành địa điểmRoutes API.

Thiết lập Nền tảng Google Maps

Nếu bạn chưa có tài khoản Google Cloud Platform và dự án có bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và dự án.

  1. Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án rồi chọn dự án mà bạn muốn dùng cho lớp học lập trình này. Chọn dự án
  2. Bật các API của Nền tảng Google Maps cần thiết cho lớp học lập trình này trong trang Thư viện Maps API. Để thực hiện việc này, hãy làm theo các bước trong video này hoặc tài liệu này.
  3. Tạo khoá API trong trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả các yêu cầu gửi đến Nền tảng Google Maps đều cần có khoá API.

3. Thiết lập dự án Node.js

Trong lớp học này, chúng ta sẽ dùng Node.js để thu thập điểm xuất phát và điểm đến từ web, đồng thời yêu cầu tuyến đường thông qua Routes API.

Giả sử bạn đã cài đặt Node.js, hãy tạo một thư mục mà bạn sẽ dùng để chạy dự án này:

$ mkdir ac_routes
$ cd ac_routes

Khởi chạy một gói Node.js mới trong thư mục cho ứng dụng của bạn:

$ npm init

Lệnh này sẽ nhắc bạn nhập một số thông tin, chẳng hạn như tên và phiên bản của ứng dụng. Hiện tại, bạn chỉ cần nhấn phím RETURN để chấp nhận các giá trị mặc định cho hầu hết các lựa chọn. Điểm truy cập mặc định là index.js, bạn có thể thay đổi thành tệp chính của mình. Trong phòng thí nghiệm này, tệp chính là function/server.js(xem thêm thông tin chi tiết ở bước 6).

Ngoài ra, bạn có thể thoải mái cài đặt khung và mô-đun mà mình muốn. Phòng thí nghiệm này sử dụng khung web(Express) và trình phân tích cú pháp nội dung(body-parser). Xem thêm thông tin chi tiết trong tệp package.json.

4. Tạo bản đồ động

Giờ đây, khi đã có phần phụ trợ Node.js, hãy cùng tìm hiểu các bước cần thiết cho phía máy khách.

  • Tạo trang HTML cho ứng dụng
  • Tạo tệp CSS để định kiểu
  • Tải API JavaScript của Google Maps vào trang HTML
  • Dán khoá API vào thẻ tập lệnh để xác thực ứng dụng của bạn
  • Tạo một tệp JavaScript để xử lý chức năng của ứng dụng

Tạo trang HTML

  1. Tạo một thư mục mới trong thư mục dự án(trong trường hợp này là ac_routes)
     $ mkdir public
     $ cd public
    
  2. Trong thư mục công khai, hãy tạo index.html
  3. Sao chép mã sau vào 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>
    

Tạo tệp CSS

  1. Tạo style.css trong thư mục public
  2. Sao chép mã sau vào 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;}
    

Tải Maps JavaScript API

Trong phòng thí nghiệm này, chúng ta sẽ sử dụng tính năng nhập thư viện động để tải API JavaScript của Maps. Hãy tìm hiểu thêm thông tin chi tiết tại đây.

Trong index.html, hãy sao chép đoạn mã sau trước thẻ đóng của phần nội dung. Thay "YOUR_API_KEY" bằng khoá API của riêng bạn.

<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>

Tạo tệp JavaScript

  1. Trong thư mục công khai, hãy tạo app.js
  2. Sao chép mã sau vào 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 là một mã nhận dạng có thể dùng cho các mẫu mã yêu cầu phải có mã Maps. Mã nhận dạng này không dành cho các ứng dụng sản xuất và không thể dùng cho những tính năng yêu cầu tạo kiểu trên đám mây. Trong lớp học này, bạn cần có Mã bản đồ cho Điểm đánh dấu nâng cao ở giai đoạn sau. Tìm hiểu thêm thông tin về cách tạo Mã bản đồ cho ứng dụng của bạn.

Trong index.html, hãy liên kết app.js trước thẻ đóng của phần nội dung và sau thẻ tập lệnh tải Maps JavaScript API.

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

Mã mẫu đầy đủ

Bạn có thể xem toàn bộ mã cho đến thời điểm này trên GitHub:step1_createDynamicMap

5. Nhập địa chỉ cho điểm xuất phát và điểm đến

  • Thêm 2 trường văn bản vào index.html để nhập điểm xuất phát và điểm đến
  • Nhập thư viện Autocomplete
  • Liên kết dịch vụ Tự động hoàn thành với các trường văn bản của điểm xuất phát và điểm đến

Thêm trường văn bản

Trong index.html, hãy thêm mã sau đây làm thành phần con đầu tiên của div có lớp 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>

Nhập và bật tính năng Tự động hoàn thành

Lớp google.maps.places.Autocomplete là một tiện ích cung cấp các cụm từ gợi ý về Địa điểm dựa trên văn bản mà người dùng nhập. Thao tác này sẽ gắn vào một phần tử đầu vào thuộc loại văn bản và lắng nghe hoạt động nhập văn bản trong trường đó. Danh sách dự đoán sẽ xuất hiện dưới dạng danh sách thả xuống và được cập nhật khi bạn nhập văn bản.

Trong app.js, hãy thêm mã sau vào sau khi khởi chạy bản đồ:

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

Sau khi người dùng chọn một địa điểm trong danh sách dự đoán tự động hoàn thành, bạn có thể truy xuất thông tin chi tiết về kết quả địa điểm bằng phương thức getPlace(). Kết quả về địa điểm chứa nhiều thông tin về một Địa điểm. Trong phòng thí nghiệm này, chúng ta sẽ sử dụng place_id để xác định địa điểm đã chọn. Mã địa điểm giúp xác định riêng một địa điểm trong cơ sở dữ liệu của Google Địa điểm và trên Google Maps. Tìm hiểu thêm thông tin về Mã địa điểm.

Thêm các kiểu có liên quan

Trong style.css, hãy thêm mã sau:

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

Mã mẫu đầy đủ

Bạn có thể xem toàn bộ mã nguồn cho đến thời điểm này trên GitHub:step2_inputAddress

6. Yêu cầu tuyến đường

  • Thêm nút "Lấy tuyến đường" vào index.html để bắt đầu yêu cầu về tuyến đường
  • Nút này kích hoạt việc gửi dữ liệu về điểm xuất phát và điểm đến đến dịch vụ Node.js
  • Dịch vụ Node.js gửi yêu cầu đến Routes API
  • Phản hồi API được trả về phía máy khách để hiển thị

Khi bạn đã đặt điểm khởi hành và điểm đến, đồng thời có một bản đồ động, thì đã đến lúc bạn nhận tuyến đường. Routes API (thế hệ tiếp theo, phiên bản được tối ưu hoá hiệu suất của dịch vụ chỉ đường và ma trận khoảng cách) sẽ giúp bạn giải quyết vấn đề này. Trong lớp học này, chúng ta sẽ dùng Node.js để thu thập điểm xuất phát và điểm đến từ web, đồng thời yêu cầu tuyến đường thông qua Routes API.

Trong index.html, hãy thêm nút "Lấy đường đi" trước thẻ đóng của div có lớp aside :

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

Trong style.css, hãy thêm dòng sau:

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

Trong app.js, hãy thêm mã bên dưới để gửi dữ liệu về điểm xuất phát và điểm đến đến dịch vụ 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() là hàm mà chúng ta sẽ dùng để vẽ tuyến đường trên bản đồ. Chúng tôi sẽ trình bày chi tiết ở bước tiếp theo.

Tạo máy chủ

Trong thư mục dự án(trong trường hợp này là ac_routes), hãy tạo một thư mục mới có tên là function. Trong thư mục này, hãy tạo một tệp có tên là server.js. Tệp này đóng vai trò là điểm truy cập của dự án mà bạn định cấu hình khi thiết lập dự án Node.js, xử lý 3 chức năng chính:

  1. Thu thập dữ liệu từ ứng dụng web
  2. Gửi yêu cầu đến Routes API
  3. Trả về phản hồi API cho phía máy khách

Sao chép mã sau vào server.js. Thay thế "YOUR_API_KEY" bằng khoá API của riêng bạn. Để tăng cường tính bảo mật của khoá API, bạn nên sử dụng một khoá riêng biệt cho phần phụ trợ. Xem Hướng dẫn về bảo mật.

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

Tìm hiểu thêm về Routes API trong bài viết lấy một tuyến đường bằng Routes API.

Chạy mã

Chạy mã dưới đây trong dòng lệnh:

$ node function/server.js

Mở trình duyệt rồi truy cập vào http://127.0.0.1:8080/index.html. Bạn sẽ thấy trang ứng dụng. Cho đến giai đoạn này, phản hồi API sẽ được trả về cho ứng dụng web. Hãy xem cách hiển thị tuyến đường trên bản đồ ở bước tiếp theo.

Mã mẫu đầy đủ

Bạn có thể xem toàn bộ mã nguồn cho đến thời điểm này trên GitHub:step3_requestRoute

7. Hiển thị tuyến đường trên bản đồ

Trong bước trước, chúng ta tham chiếu đến renderRoutes() khi nhận được phản hồi thành công từ dịch vụ Node.js. Bây giờ, hãy thêm mã thực tế để hiển thị tuyến đường trên bản đồ.

Trong app.js, hãy thêm mã bên dưới:

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

Routes API trả về polyline ở định dạng encodedPolyline(mặc định) hoặc geoJsonLinestring. Trong lớp học này, chúng ta sẽ sử dụng định dạng encodedPolyline và giải mã bằng cách dùng thư viện hình học JavaScript của Maps.

Chúng ta sẽ dùng addMarker() để thêm các điểm đánh dấu nâng cao cho điểm xuất phát và điểm đến. Trong app.js, hãy thêm mã sau:

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

Ở đây, chúng ta sẽ tạo 2 điểm đánh dấu nâng cao – A cho điểm xuất phát và B cho điểm đến. Tìm hiểu thêm về các điểm đánh dấu nâng cao.

Tiếp theo, chúng ta sẽ đặt chế độ xem bản đồ ở giữa tuyến đường đã truy xuất, bằng cách sử dụng thông tin viewport (khung hiển thị) thuận tiện do Routes API cung cấp. Trong app.js, hãy thêm mã sau:

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

Mã mẫu đầy đủ Bạn có thể xem toàn bộ mã cho đến thời điểm này trên GitHub:step4_displayRoute

8. Xoá các phần tử khỏi bản đồ

Tại đây, chúng ta muốn tiến thêm một bước nữa. Hãy xoá bản đồ trước khi vẽ các điểm đánh dấu và tuyến đường mới để tránh bị rối mắt.

Trong app.js, hãy thêm một hàm khác:

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

Thêm dòng sau vào đầu renderRoutes():

clearUIElem(paths,'polyline');

Thêm dòng sau vào đầu addMarker():

clearUIElem(markers,'advMarker');

Mã mẫu đầy đủ

Bạn có thể xem toàn bộ mã nguồn cho đến thời điểm này trên GitHub:step5_removeElements

9. Xin chúc mừng

Bạn đã tạo thành công đối tượng.

Kiến thức bạn học được

  • Bật API Nền tảng Google Maps
  • Tải API JavaScript của Google Maps vào trang HTML
  • Nhập Thư viện địa điểm, Maps JavaScript API
  • Liên kết dịch vụ Place Autocomplete với các trường văn bản
  • Yêu cầu một tuyến đường thông qua Routes API
  • Hiển thị tuyến đường trên bản đồ động
  • Tạo mã bản đồ
  • Tạo điểm đánh dấu nâng cao

Tìm hiểu thêm

Bạn muốn xem những lớp học lập trình nào khác?

Trực quan hoá dữ liệu trên bản đồ Tìm hiểu thêm về cách tuỳ chỉnh kiểu bản đồ Tạo các tương tác 3D trên bản đồ

Bạn không thấy khoá học lập trình mà mình muốn trong danh sách trên? Yêu cầu cấp lại bằng cách báo lỗi mới tại đây.