1. 概览
无论您是准备开启公路旅行、规划日常通勤路线,还是在繁华的城市中导航,从 A 点前往 B 点不仅仅是知道要去哪里。可靠的路线生成工具至关重要。
借助 Google Maps Platform,您可以向应用中添加动态地图,让用户通过自动补全功能快速输入位置信息,并在地图上显示路线。
此 Codelab 将引导开发者使用 Maps JavaScript API、地点自动补全和 Routes API 构建 Web 应用。您将通过可自定义的教程了解如何集成多个 Google Maps Platform API。
构建内容
此 Codelab 将指导您使用 HTML、CSS、JavaScript 和 Node.js 后端构建 Web 应用。


学习内容
- 如何启用 Google Maps Platform API
- 如何将动态地图集成到 Web 应用中
- 如何集成地点自动补全服务
- 如何通过 Routes API 请求路线
- 如何在动态地图上显示路线
- 如何创建地图 ID
- 如何向动态地图添加高级标记
所需条件
- Google Cloud 项目
- Maps JavaScript API
- Places API(地点自动补全)
- Routes API
- 具备 HTML 和 CSS 方面的基础知识
- 熟悉 JavaScript/Node.js 的使用
示例代码
GitHub 上提供了完整的解决方案和分步代码。代码不包含必需的 Node 软件包。在执行代码之前,请安装必要的依赖项。所需软件包的详细信息可在 package.json 文件中找到(第 3 步中对此进行了说明)。
2. 设置项目并启用 API
在启用步骤中,您需要启用 Maps JavaScript API、Place Autocomplete 和 Routes API。
设置 Google Maps Platform
如果您还没有已启用结算功能的 Google Cloud Platform 账号和项目,请参阅 Google Maps Platform 使用入门指南,创建结算账号和项目。
3. 设置 Node.js 项目
在本实验中,我们将使用 Node.js 从网页收集起点和终点,并通过 Routes API 请求路线。
假设您已安装 Node.js,请创建一个将用于运行此项目的目录:
$ mkdir ac_routes
$ cd ac_routes
在应用目录中初始化新的 Node.js 软件包:
$ npm init
此命令会提示您输入多项信息,例如应用的名称和版本。目前,您可以直接按 RETURN 键接受大多数默认值。默认入口点为 index.js,您可以将其更改为您的主文件。在此实验中,主文件为 function/server.js(详情请参阅第 6 步)。
此外,您还可以随意安装自己喜欢的框架和模块。此实验使用 Web 框架(Express) 和正文解析器(body-parser)。如需了解详情,请参阅 package.json 文件。
4. 创建动态地图
现在,我们已经设置了 Node.js 后端,接下来我们来了解一下客户端所需的步骤。
- 为应用创建 HTML 网页
- 创建用于设置样式的 CSS 文件
- 将 Google Maps JavaScript API 加载到 HTML 页面中
- 将您的 API 密钥粘贴到脚本标记中,以对您的应用进行身份验证
- 创建 JavaScript 文件来处理应用功能
创建 HTML 网页
- 在项目文件夹(在本例中为 ac_routes)中创建一个新目录
$ mkdir public $ cd public - 在 public 目录中,创建 index.html
- 将以下代码复制到 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 文件
- 在 public 目录中创建 style.css
- 将以下代码复制到 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;}
加载 Maps JavaScript API
在本实验中,我们将使用动态库导入来加载 Maps 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 文件
- 在 public 目录中,创建 app.js
- 将以下代码复制到 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 是一个可用于需要地图 ID 的代码示例的 ID。此 ID 不适合在正式版应用中使用,也不能用于需要云端样式的功能。在此实验中,我们要求在后期阶段为高级标记提供地图 ID。详细了解如何为应用创建地图 ID。
关联 JavaScript 文件
在 index.html 中,将 app.js 链接到 body 结束标记之前,并放在加载 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 类是一个根据用户文本输入提供地点预测的 widget。它会附加到文本类型的输入元素,并监听该字段中的文本输入。预测结果列表以下拉列表的形式显示,并随着文本的输入而更新。
在 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 来标识所选地点。地点 ID 可唯一标识 Google Places 数据库中和 Google 地图上的地点。详细了解地点 ID。
添加相关样式
在 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 服务向 Routes API 发送请求
- 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 项目时进行配置,并处理以下三个关键功能:
- 从 Web 客户端收集数据
- 向 Routes API 发送请求
- 将 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 响应会返回给 Web 客户端。在下一步中,我们将了解如何在地图上显示路线。
完整示例代码
截至目前为止的完整代码可在 GitHub 上找到:step3_requestRoute
7. 在地图上显示路线
在上一步中,我们是指成功从 Node.js 服务收到响应时的 renderRoutes()。现在,我们来添加实际代码,以在地图上显示路线。
在 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");
}
});
}
Routes API 会以 encodedPolyline(默认)或 geoJsonLinestring 格式返回多段线。在此实验中,我们将使用 encodedPolyline 格式,并使用 Maps JavaScript 几何库对其进行解码。
我们将使用 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 代表目的地。详细了解高级标记。
接下来,我们将使用 Routes 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. 恭喜
您已成功构建该事物。
要点回顾
- 启用 Google Maps Platform API
- 将 Google Maps JavaScript API 加载到 HTML 页面中
- 导入地点库 (Maps JavaScript API)
- 将地点自动补全服务绑定到文本字段
- 通过 Routes API 请求路线
- 在动态地图上显示路线
- 创建地图 ID
- 创建高级标记
了解详情
您还想查看其他哪些 Codelab?
上面没有列出您希望了解的 Codelab?没关系,请在此处通过创建新问题的方式申请 Codelab。
