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
目录。