每个物流仪表板、房地产列表和食品配送应用都有一个共同点:地图。如果您的网站还没有地图,您就错失了关键信息。这些信息可以帮助用户一目了然地理解位置、邻近性和空间关系。
本教程将指导您向任何 JavaScript 项目添加一个完全交互式的地图。您将从渲染的地图开始,添加标记和弹出窗口,使用地理编码 API 连接地址搜索,最后完成一个生产就绪的 React 组件,可以完美集成到 Next.js。
以下是您最终将构建的内容:
- 使用您的 API 密钥认证的实时矢量地图
- 具有自定义弹出内容的可点击标记
- 由地理编码支持的地址搜索功能
- 具有适当清理和 Next.js SSR 处理的可重用 React 组件
- 生产环境的性能优化清单
前置要求
开始之前,请确保您具有:
- MapAtlas API 密钥 (免费注册,不需要信用卡)。这个密钥认证所有 MapAtlas 服务:瓦片、地理编码和路由。
- JavaScript 项目。纯 HTML、React、Vue 或 Svelte 都可以。
- Node.js 18+(如果通过 npm 安装)。
步骤 1:安装 MapAtlas SDK
使用 npm 将 SDK 拉入您的项目:
npm install @mapmetrics/mapmetrics-gl
如果您使用的是纯 HTML 页面,请将 CDN 链接放入您的 <head>:
<link rel="stylesheet" href="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css" />
<script src="https://unpkg.com/@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.js"></script>
不要跳过 CSS 导入。 没有它,地图控件和弹出窗口会无样式呈现。功能正常,但在视觉上不完整。
步骤 2:创建地图容器
SDK 会填充您指向的任何元素,因此您需要一个具有明确高度的 <div>。这是最常见的设置错误:如果容器的 height: 0,地图会初始化但保持不可见。
<div id="map" style="width: 100%; height: 500px;"></div>
固定像素值或视口单位(100vh、50vh)都有效。百分比高度仅在父元素也具有定义的高度时才有效。
步骤 3:呈现您的第一个交互式地图
只需三行配置即可:一个容器、一个带有您的 API 密钥的样式 URL 和一个起始位置。
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const map = new mapmetricsgl.Map({
container: 'map',
style: 'https://tiles.mapatlas.eu/styles/basic/style.json?key=YOUR_API_KEY',
center: [4.9041, 52.3676],
zoom: 12,
});
打开页面,您将看到一个可以拖动、滚动和捏合导航的矢量地图。瓦片从 MapAtlas 服务器按需加载,因此没有繁重的初始下载。
选择地图样式
交换样式 URL 中的路径段以完全改变外观:
| 样式 | URL 路径 | 最适合 |
|---|---|---|
| Basic | /styles/basic/style.json | 通用应用 |
| Bright | /styles/bright/style.json | 数据可视化叠加 |
| Dark | /styles/dark/style.json | 仪表板、深色模式、分析 |
快速获胜: 对于管理面板和在低光环境中使用的工具使用深色样式。它可以减少眼睛疲劳,使热力图和路线等数据层在背景中视觉上凸显。
步骤 4:向地图添加标记和弹出窗口
没有标记的地图就是背景图像。标记将静态视图转变为用户可以交互的东西。
带有弹出窗口的单个标记
const popup = new mapmetricsgl.Popup().setHTML(`
<strong>Amsterdam Central</strong>
<p>Stationsplein, 1012 AB Amsterdam</p>
`);
new mapmetricsgl.Marker({ color: '#97C70A' })
.setLngLat([4.9001, 52.3791])
.setPopup(popup)
.addTo(map);
点击标记后,弹出窗口就会打开。您可以在内部放置任何 HTML:地址、缩略图、CTA 按钮,无论您的 UI 需要什么。
从数据绘制多个标记
大多数真实应用需要超过一个标记。循环遍历数组并为每个条目创建一个标记:
const locations = [
{ name: 'Amsterdam', coords: [4.9041, 52.3676] },
{ name: 'Rotterdam', coords: [4.4777, 51.9244] },
{ name: 'Utrecht', coords: [5.1214, 52.0907] },
];
locations.forEach(({ name, coords }) => {
const popup = new mapmetricsgl.Popup().setHTML(`<strong>${name}</strong>`);
new mapmetricsgl.Marker({ color: '#97C70A' })
.setLngLat(coords)
.setPopup(popup)
.addTo(map);
});
性能说明: 一旦超过大约 100 到 200 个标记,在缩小视图中的渲染速度会明显变慢。启用 GeoJSON 源集群(SDK 本身支持)以在低缩放级别处对附近标记进行分组。检查SDK 文档了解集群配置。
步骤 5:使用地理编码 API 添加地址搜索
地理编码 API 将文本查询(街道地址、城市名称或地标)转换为您可以平移到、标记或馈送到路由请求的坐标。
async function searchAddress(query) {
const url = new URL('https://api.mapatlas.eu/geocoding/v1/search');
url.searchParams.set('text', query);
url.searchParams.set('key', 'YOUR_API_KEY');
const res = await fetch(url);
const data = await res.json();
if (!data.features.length) return;
const [lng, lat] = data.features[0].geometry.coordinates;
const label = data.features[0].properties.label;
map.flyTo({ center: [lng, lat], zoom: 14 });
new mapmetricsgl.Marker({ color: '#97C70A' })
.setLngLat([lng, lat])
.setPopup(new mapmetricsgl.Popup().setHTML(`<strong>${label}</strong>`))
.addTo(map);
}
searchAddress('Rijksmuseum, Amsterdam');
结果作为 GeoJSON 要素返回,因此它们可直接插入任何 GeoJSON 兼容的层、数据表或下游 API 调用。
在 30 行以下构建实时搜索栏: 将
searchAddress附加到文本字段的input事件,以 300ms 的速率防抖,您就拥有了无额外依赖的自动完成样式地图搜索。
使用 React 集成交互式地图
可重用的地图组件
将地图初始化包装在 useEffect 中,使其在 DOM 挂载后运行,并返回一个清理函数以防止卸载时的内存泄漏:
import { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
export function MapAtlasMap({ apiKey, center = [4.9041, 52.3676], zoom = 12 }) {
const containerRef = useRef(null);
useEffect(() => {
const map = new mapmetricsgl.Map({
container: containerRef.current,
style: `https://tiles.mapatlas.eu/styles/basic/style.json?key=${apiKey}`,
center, zoom,
});
return () => map.remove();
}, [apiKey]);
return <div ref={containerRef} style={{ width: '100%', height: '500px' }} />;
}
在您的组件树中的任何地方使用它:
<MapAtlasMap apiKey={process.env.NEXT_PUBLIC_MAPATLAS_KEY} center={[4.9041, 52.3676]} zoom={13} />
处理 Next.js 服务器端呈现
地图 SDK 依赖于在 SSR 期间不存在的浏览器 API(window、document)。使用禁用 SSR 的动态导入组件:
import dynamic from 'next/dynamic';
const MapAtlasMap = dynamic(
() => import('./MapAtlasMap').then(m => m.MapAtlasMap),
{ ssr: false, loading: () => (<div style={{ height: 500, background: '#f0f1f3', borderRadius: 12 }} />) }
);
loading 占位符在地图包下载时保持布局稳定,防止累积布局偏移(CLS),这对用户体验和 Core Web Vitals 都很重要。
生产环境性能清单
发布前,请进行以下优化:
- 延迟加载视口下方的地图。 使用
IntersectionObserver仅在其容器滚动到视图中时初始化地图。这会从初始页面加载中推迟约 200 KB 的 JavaScript。 - 坚持使用矢量瓦片。 矢量瓦片可以清晰地扩展到任何屏幕密度,加载速度比光栅图像快,并且可以在客户端完全重新设置样式,无需额外的服务器请求。MapAtlas 默认提供矢量瓦片。
- 对大型标记集进行集群。 超过 100 到 200 个标记后,在缩小视图中未集群的呈现会导致明显的帧丢弃。集群完全解决了这个问题。
- 保持 API 密钥服务器端。 永远不要将密钥提交到公开存储库。对敏感操作使用环境变量(Next.js 中的
NEXT_PUBLIC_MAPATLAS_KEY)或通过后端代理请求。 - 为区域应用设置
maxBounds。 如果您的用户只关心一个地理位置,请限制视口,以便永远不会请求该区域外的瓦片。更少的网络调用,更快的加载。
接下来构建什么
您有一个可以呈现、显示标记、搜索地址和与 React 集成的地图。从这里前进:
- 路由 API:请求两个坐标之间的转向导航。返回路线折线、总距离和估计的行驶时间。
- 等时线 API:生成一个覆盖在 n 分钟内可达到的每个点的多边形。用于配送区域、服务覆盖地图和流域面积分析。
- 矩阵 API:在单个请求中计算多个起点和目标地点之间的行驶时间和距离。对于车队调度和物流优化至关重要。
完整的 SDK 参考、样式文档和 API 指南可在 docs.mapatlas.xyz 获得。
常见问题
我可以免费向我的网站添加交互式地图吗?
可以。MapAtlas 提供免费级别,注册时无需信用卡。它包括矢量瓦片呈现、地理编码 API 和路由 API。这足以满足开发和小规模生产使用。
如何在 React 或 Next.js 应用中嵌入地图?
将地图初始化包装在 useEffect 钩子中,使其在 DOM 挂载后运行。在 Next.js 中,使用 dynamic() 和 ssr: false 以避免服务器端呈现错误。本教程中以复制粘贴示例涵盖了这两种方法。
什么是矢量瓦片,为什么我应该使用它们而不是光栅?
矢量瓦片将地图要素(道路、建筑物、标签)描述为数学几何图形,而不是预呈现的像素图像。它们可以清晰地扩展到任何分辨率、下载速度更快,并且可以在客户端完全重新设置样式,无需额外的服务器往返。
在性能下降之前我可以添加多少个标记?
渲染通常在低缩放级别处超过 100 到 200 个标记时会下降。解决方案是集群:MapAtlas SDK 本身支持 GeoJSON 源集群,在低缩放级别处对附近标记进行分组,并在用户缩放时展开。
使用 MapAtlas 需要 GIS 经验吗?
不需要。SDK 是为网络开发人员而非 GIS 专家设计的。您使用坐标和缩放级别初始化地图,使用经度/纬度对添加标记,并使用纯文本调用地理编码 API。不需要空间数据库或 GIS 工具。
