เกี่ยวกับ Codelab นี้
1 准备工作
在此 Codelab 中,您将学习开始使用适用于 Google Maps JavaScript API 的 vis.gl/react-google-map
库所需的各种知识,您可以通过该库向 React 应用添加 Google 地图。您将学习如何进行设置、加载 Maps JavaScript API、显示第一个地图、使用标记和标记聚类、在地图上绘图以及处理用户互动。
前提条件
- 具备 JavaScript、HTML 和 CSS 方面的基础知识
学习内容
- 如何开始使用适用于 Google Maps Platform 的
vis.gl/react-google-map
库。 - 如何以声明方式加载 Maps JavaScript API。
- 如何在 React 应用中加载地图。
- 如何使用标记、自定义标记和标记聚类。
- 如何使用 Maps JavaScript API 事件系统提供用户互动功能。
- 如何动态控制地图。
- 如何在地图上绘图。
所需条件
- 已启用结算功能的 Google Cloud 账号。
- 已启用 Maps JavaScript API 的 Google Maps Platform API 密钥。
- 计算机上已安装 Node.js。
- 您常用的文本编辑器或 IDE。
- 适用于 Google Maps JavaScript API 的
vis.gl/react-google-map
库。 googlemaps/markerclusterer
库。
设置 Google Maps Platform
如果您还没有已启用结算功能的 Google Cloud Platform 账号和项目,请参阅 Google Maps Platform 使用入门指南,创建结算账号和项目。
- 在 Cloud Console 中,点击项目下拉菜单,选择要用于此 Codelab 的项目。
2 进行设置
下载入门级项目
如需下载入门级项目模板和解决方案代码,请按照以下步骤操作:
- 下载或复刻 GitHub 代码库。入门级项目位于
/starter
目录中,其中包含完成此 Codelab 所需的基本文件结构。您将在/starter/src
目录中完成所有操作。
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-react-js.git
或者,您也可以点击下方按钮来下载源代码。
- 找到
/starter
目录,并安装 npm。此操作会安装package.json
文件中列出的所有所需依赖项。
cd starter && npm install
- 在
/starter
目录中执行以下命令:
npm start
入门级项目已设置完毕,您可以使用 Vite 开发服务器编译并运行您在本地编写的代码。Vite 开发服务器还会在您更改代码时自动在浏览器中重新加载您的应用。如果点击构建流程结束时显示的链接,应该会看到一个网页,上面写着“Hello, world!”
- 若要运行完整解决方案代码,请找到
/solution
目录并完成相同的设置步骤。
3 加载 Maps JavaScript API
Maps JavaScript API 是使用网页版 Google Maps Platform 的基础。此 API 提供 JavaScript 接口,方便您使用
Google Maps Platform 的所有功能,包括地图、标记、绘图工具以及其他 Google Maps Platform 服务(例如地点)。
若要使用 React 框架加载 Maps JavaScript API,您需要使用 vis.gl/react-google-map
库中的 APIProvider
组件。可以在应用的任何层级添加该组件,通常是在顶层的某个位置,它会渲染所有未经修改的子级组件。除了处理 Maps JavaScript API 的加载之外,它还会为该库的其他组件和钩子提供上下文信息和函数。APIProvider
包含在 vis.gl/react-google-map
库中,因此之前运行 npm install
时就已安装该组件。
如需使用 APIProvider
组件,请按照以下步骤操作:
- 打开
/src/app.tsx
文件。您将在此文件中完成此 Codelab 的所有操作。 - 在文件顶部,导入
@
vis.gl/react-google-maps
库中的APIProvider
类:
import {APIProvider} from '@vis.gl/react-google-maps';
- 在
App
函数定义中,使用在上一步中创建的 API 密钥设置APIProvider
组件的apiKey
参数,同时利用控制台日志消息设置onLoad
属性:
<APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
APIProvider
组件接受一系列属性,这些属性用于指定加载 Maps JavaScript API 的各种选项,包括 Google Maps Platform API 密钥、要加载的 API 版本,以及要加载的由 Maps JavaScript API 提供的任何其他库。
Google Maps API 密钥是让 APIProvider
正常发挥作用所需的唯一属性,我们添加 onLoad
属性是为了进行演示。如需了解详情,请参阅 <APIProvider>
组件。
您的 app.tsx
文件应如下所示:
import React from 'react';
import {createRoot} from "react-dom/client";
import {APIProvider} from '@vis.gl/react-google-maps';
const App = () => (
<APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
<h1>Hello, world!</h1>
</APIProvider>
);
const root = createRoot(document.getElementById('app'));
root.render(<App />);
export default App;
如果一切顺利,您应该会在浏览器控制台中看到 console.log
语句。Maps JavaScript API 现已加载,您可以在后续步骤中渲染动态地图。
4 显示地图
是时候显示您的第一个地图了!
Maps JavaScript API 中最常用的部分是 google.maps.Map
类,我们通过该类创建和操作地图实例。vis.gl/react-google-map
库将此类封装在 Map
组件中。首先,导入 Map
和 MapCameraChangedEvent
类。
import {APIProvider, Map, MapCameraChangedEvent} from '@vis.gl/react-google-maps';
Map
组件支持各种不同的地图设置。对于此 Codelab,您可以使用以下设置:
defaultCenter
:可设置地图中心的纬度和经度。defaultZoom
:可设置地图的初始缩放级别。- 若要显示地图,请在
APIProvider
标记中输入以下代码,让澳大利亚悉尼居于地图中心,并将缩放级别设为13
,这正好是显示城市中心的正确缩放级别:
<Map
defaultZoom={13}
defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
onCameraChanged={ (ev: MapCameraChangedEvent) =>
console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
}>
</Map>
现在,您应该可以在浏览器中看到悉尼地图了!
总结一下,在此部分中,您使用 <Map>
组件显示了地图,并通过属性设置了地图的初始状态。另外,您还使用事件在相机发生变化时捕获信息。
您的 app.tsx
文件应如下所示:
import React from 'react';
import {createRoot} from "react-dom/client";
import {APIProvider, Map, MapCameraChangedEvent} from '@vis.gl/react-google-maps';
const App = () => (
<APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
<Map
defaultZoom={13}
defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
onCameraChanged={ (ev: MapCameraChangedEvent) =>
console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
}>
</Map>
</APIProvider>
);
const root = createRoot(document.getElementById('app'));
root.render(<App />);
export default App;
5 添加云端地图样式设置
使用云端地图样式设置是使用高级标记的必要条件,您可以使用高级标记在悉尼地图上标记地图注点。
您可以使用云端地图样式设置自定义地图样式。
创建地图 ID
如果您尚未使用关联的地图样式创建地图 ID,请参阅地图 ID 指南完成以下步骤:
- 创建地图 ID。
- 将地图 ID 与地图样式相关联。
若要使用您创建的地图 ID,请设置 <Map>
组件的 mapId
属性:
<Map
defaultZoom={13}
defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
mapId='YOUR_MAP_ID'
onCameraChanged={ (ev: MapCameraChangedEvent) =>
console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
}>
</Map>
您应该会在地图上看到所选的样式!
6 向地图添加标记
虽然开发者可以通过 Maps JavaScript API 执行很多操作,但是在地图上添加标记绝对是最受欢迎的操作。标记可用于在地图上标识特定地点,而且是用于处理用户互动的常用界面元素。如果之前使用过 Google 地图,那么您可能熟悉默认标记,如下所示:
若要使用 AdvancedMarker
组件在地图上添加标记,请按照以下步骤操作:
- 创建对象列表来代表悉尼地区的地图注点,将该列表放置在导入正下方,但要放在
App
定义之外:
type Poi ={ key: string, location: google.maps.LatLngLiteral }
const locations: Poi[] = [
{key: 'operaHouse', location: { lat: -33.8567844, lng: 151.213108 }},
{key: 'tarongaZoo', location: { lat: -33.8472767, lng: 151.2188164 }},
{key: 'manlyBeach', location: { lat: -33.8209738, lng: 151.2563253 }},
{key: 'hyderPark', location: { lat: -33.8690081, lng: 151.2052393 }},
{key: 'theRocks', location: { lat: -33.8587568, lng: 151.2058246 }},
{key: 'circularQuay', location: { lat: -33.858761, lng: 151.2055688 }},
{key: 'harbourBridge', location: { lat: -33.852228, lng: 151.2038374 }},
{key: 'kingsCross', location: { lat: -33.8737375, lng: 151.222569 }},
{key: 'botanicGardens', location: { lat: -33.864167, lng: 151.216387 }},
{key: 'museumOfSydney', location: { lat: -33.8636005, lng: 151.2092542 }},
{key: 'maritimeMuseum', location: { lat: -33.869395, lng: 151.198648 }},
{key: 'kingStreetWharf', location: { lat: -33.8665445, lng: 151.1989808 }},
{key: 'aquarium', location: { lat: -33.869627, lng: 151.202146 }},
{key: 'darlingHarbour', location: { lat: -33.87488, lng: 151.1987113 }},
{key: 'barangaroo', location: { lat: - 33.8605523, lng: 151.1972205 }},
];
const App = () => (
...
);
- 使用
<Pin>
元素自定义图钉:
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
- 创建自定义组件,以渲染包含高级标记的列表,将该列表放置在
App
定义下方:
const App = () => (
...
);
const PoiMarkers = (props: {pois: Poi[]}) => {
return (
<>
{props.pois.map( (poi: Poi) => (
<AdvancedMarker
key={poi.key}
position={poi.location}>
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>
))}
</>
);
};
- 添加
PoiMarkers
组件作为Map
组件的子级:
<Map
... map properties ...
>
<PoiMarkers pois={locations} />
</Map>
- 最后,向导入添加
Pin
和AdvancedMarker
。
import {
APIProvider,
Map,
AdvancedMarker,
MapCameraChangedEvent,
Pin
} from '@vis.gl/react-google-maps';
您应该会在地图上看到自定义高级标记:
7 启用标记聚类
使用多个标记或标记彼此靠得很近时,您可能会遇到如下问题:标记重叠或看起来过于拥挤,导致用户体验不佳。例如,在上一步创建标记后,您可能已注意到此问题:
这正是标记聚类的用武之地。标记聚类是另一种经常实现的地图项,可将临近的标记组合为单个图标,并且该图标能够随缩放级别而改变,如下所示:
标记聚类算法会将地图的可见区域划分为网格,然后对位于同一单元格的图标进行聚类。幸运的是,您不必为任何操作担心,因为 Google Maps Platform 团队创建了一个实用的开源实用程序库 MarkerClustererPlus
,可以自动为您执行各项操作。您可以在 GitHub 上查看 MarkerClustererPlus
库的源代码。
若要启用标记聚类,请按照以下步骤操作:
- 在
app.tsx
文件顶部,我们更新导入和支持类型并将其添加到库中。
import React, {useEffect, useState, useRef, useCallback} from 'react';
import {createRoot} from "react-dom/client";
import {
APIProvider,
Map,
AdvancedMarker,
MapCameraChangedEvent,
useMap,
Pin
} from '@vis.gl/react-google-maps';
import {MarkerClusterer} from '@googlemaps/markerclusterer';
import type {Marker} from '@googlemaps/markerclusterer';
对于此 Codelab 的模板项目,package.json
文件中声明的依赖项已包含 MarkerClustererPlus
实用程序库,因此,当您在此 Codelab 开头运行 npm install
时就已安装了该库。
- 为
PoiMarkers
组件中的MarkerClusterer
和支持元素创建变量。
您需要使用地图实例来初始化 MarkerClusterer
。从 useMap()
钩子中获取该实例:
const map = useMap();
- 创建存储在状态变量中的标记列表:
const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
- 存储聚类器以用作参考:
const clusterer = useRef<MarkerClusterer | null>(null);
- 此外,在
PoiMarkers
组件中,创建MarkerClusterer
实例并将其传递给要展示标记聚类器的Map
实例:
useEffect(() => {
if (!map) return;
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({map});
}
}, [map]);
- 创建可在标记列表发生变化时更新聚类器的效果:
useEffect(() => {
clusterer.current?.clearMarkers();
clusterer.current?.addMarkers(Object.values(markers));
}, [markers]);
- 创建函数来创建对新标记的引用:
const setMarkerRef = (marker: Marker | null, key: string) => {
if (marker && markers[key]) return;
if (!marker && !markers[key]) return;
setMarkers(prev => {
if (marker) {
return {...prev, [key]: marker};
} else {
const newMarkers = {...prev};
delete newMarkers[key];
return newMarkers;
}
});
};
- 在
AdvancedMarker
元素中使用该方法创建对每个标记的引用。
<AdvancedMarker
key={poi.key}
position={poi.location}
ref={marker => setMarkerRef(marker, poi.key)}
>
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>
您现在应该会在地图上看到标记聚类器:
如果您放大和缩小地图,MarkerClustererPlus
会自动重新调整聚类器的编号和大小。您还可以点击任意标记聚类器图标进行放大,查看相应聚类器包含的所有标记。
总结一下,在此部分中,您导入了开源 MarkerClustererPlus
实用程序库,并使用它创建了 MarkerClusterer
实例,在 React 状态和引用的帮助下,该实例可自动将您在上一步中创建的标记加入聚类。
您的 PoiMarkers
组件应如下所示:
const PoiMarkers = (props: { pois: Poi[] }) => {
const map = useMap();
const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
const clusterer = useRef<MarkerClusterer | null>(null);
// Initialize MarkerClusterer, if the map has changed
useEffect(() => {
if (!map) return;
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({map});
}
}, [map]);
// Update markers, if the markers array has changed
useEffect(() => {
clusterer.current?.clearMarkers();
clusterer.current?.addMarkers(Object.values(markers));
}, [markers]);
const setMarkerRef = (marker: Marker | null, key: string) => {
if (marker && markers[key]) return;
if (!marker && !markers[key]) return;
setMarkers(prev => {
if (marker) {
return {...prev, [key]: marker};
} else {
const newMarkers = {...prev};
delete newMarkers[key];
return newMarkers;
}
});
};
return (
<>
{props.pois.map( (poi: Poi) => (
<AdvancedMarker
key={poi.key}
position={poi.location}
ref={marker => setMarkerRef(marker, poi.key)}
>
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>
))}
</>
);
};
接下来,您将学习如何处理用户互动。
8 添加用户互动
您现在有一个精美地图,上面显示了悉尼最热门的一些旅游目的地。在此部分中,您将通过 Maps JavaScript API 的事件系统对用户互动添加一些额外处理,以便进一步改善地图的用户体验。
Maps JavaScript API 提供全面的事件系统,该系统采用 JavaScript 事件处理脚本,可让您通过代码处理各种用户互动。例如,您可以创建事件监听器以触发互动(例如,用户点击地图和标记、平移地图的视图、放大和缩小等)的代码执行。
若要向标记添加 click
监听器,然后以程序化方式实现地图平移,以便点击的标记显示在地图中心,请按照以下步骤操作:
- 创建
click
处理脚本回调。
在 PoiMarkers
组件中,使用 React 的 useCallback()
定义 click
处理脚本。
每当用户点击或点按标记时,系统就会触发 click
事件,并且以 JSON 对象的形式返回事件,提供所点击界面元素的相关信息。为了改善地图的用户体验,您可以处理 click
事件并使用其 LatLng
对象来获取所点击标记的纬度和经度。
获得纬度和经度之后,将其传递给 Map
实例的内置 panTo()
函数,并在事件处理脚本的回调函数中添加以下代码,即可顺畅平移地图,将所点击的标记重新设为地图中心:
const PoiMarkers = (props: { pois: Poi[] }) => {
...
const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
if(!map) return;
if(!ev.latLng) return;
console.log('marker clicked:', ev.latLng.toString());
map.panTo(ev.latLng);
});
...
};
- 为标记分配
click
处理脚本。
vis.gl/react-google-map
库的 AdvancedMarker
元素公开了以下两个有助于处理点击的属性:
clickable
:如果为 true,AdvancedMarker
将可以点击,并会触发gmp-click
事件,还可以进行互动以实现无障碍功能。例如,通过箭头键进行键盘导航。onClick
:发生click
事件时调用的回调函数。
- 更新
PoiMarkers
渲染,为每个标记分配click
处理脚本:
return (
<>
{props.pois.map( (poi: Poi) => (
<AdvancedMarker
... other properties ...
clickable={true}
onClick={handleClick}
>
...
</AdvancedMarker>
))}
</>
);
- 前往浏览器,然后点击标记。当用户点击标记时,您应该会看到地图自动平移以重新设置中心。
总结一下,在此部分中,您使用了 React 的事件系统为地图上的所有标记分配 click
处理脚本,从已触发的 click
事件中检索标记的纬度和经度,并且借此在用户点击标记时重新设置地图中心。
就差一步了!接下来,您可以利用 Maps JavaScript API 的绘图功能,进一步改善地图的用户体验。
9 在地图上绘制
到目前为止,您已创建了悉尼地图,上面显示了热门旅游目的地的标记并处理了用户互动。在此 Codelab 的最后一步中,您将使用 Maps JavaScript API 的绘图功能,为地图体验添加其他实用地图项。
想象一下,此地图将供希望探索悉尼这座城市的用户使用。实用地图项旨在直观展示所点击标记周围一定半径范围内的区域。这样,用户便可以了解从所点击的标记可以步行至其他哪些目的地。
Maps JavaScript API 包括一组用于在地图上绘制形状(例如方形、多边形、线条和圆形)的函数。通过 vis.gl/react-google-map
库,您可以在 React 中使用这些功能。
接下来,您将渲染一个圆形,当用户点击标记时,显示该标记周围半径为 800 米(大约半英里)的圆形区域。
入门级代码库包含一个适用于 circle
元素的自定义组件。您可以在 src/components/circle.tsx
文件中找到它。
若要让用户在地图上绘图,请按照以下步骤操作:
- 更新导入,以添加所提供的 Circle 组件。
import {Circle} from './components/circle'
- 为圆心创建状态变量。
在 PoiMarkers
组件中捕获圆心的状态。您将初始状态设为 null,并且清楚,除非具有有效的中心位置(和半径),否则圆形不会渲染。
const PoiMarkers = (props: { pois: Poi[] }) => {
...
const [circleCenter, setCircleCenter] = useState(null)
...
};
- 在处理
click
事件时更新圆心。
通过在事件对象中找到的位置调用 setCircleCenter
:
const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
...
setCircleCenter(ev.latLng);
});
Maps JavaScript API 中的绘图函数提供各种选项,用于展现所绘对象在地图上的显示方式。若要渲染一定半径范围内的圆形区域,请设置圆形元素的属性,例如颜色、笔触粗细度(此时圆形应该居中)以及半径。
- 在渲染中添加一个圆形,并将圆心绑定到状态变量。渲染应如下所示:
return (
<>
<Circle
radius={800}
center={circleCenter}
strokeColor={'#0c4cb3'}
strokeOpacity={1}
strokeWeight={3}
fillColor={'#3b82f6'}
fillOpacity={0.3}
/>
{props.pois.map( (poi: Poi) => (
<AdvancedMarker
key={poi.key}
position={poi.location}
ref={marker => setMarkerRef(marker, poi.key)}
clickable={true}
onClick={handleClick}
>
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>
))}
</>
);
};
大功告成!前往浏览器,然后点击某个标记。您应该会看到渲染了该标记周围一定半径范围内的圆形区域:
10 恭喜
通过适用于 Google Maps Platform 的 vis.gl/react-google-map
库,您构建了首个 Web 应用,包括加载 Maps JavaScript API、加载地图、使用标记、在地图上进行控制和绘图以及添加用户互动。
若要查看已完成的代码,请参阅 /solutions
目录。