地图与地理定位 是做什么的?
规则
- Mapbox GL JS 用于丰富的交互式地图 —
react-map-gl 作为 React 封装
- Leaflet 作为免费替代方案 —
react-leaflet 用于 React,基础瓦片无需 API 密钥
- API 密钥管理:
NEXT_PUBLIC_MAPBOX_TOKEN 用于客户端 Mapbox,切勿暴露服务器端密钥
- 地理编码:将地址转换为坐标 — Mapbox Geocoding API 或 OpenStreetMap Nominatim(免费)
- 标记聚类:使用
supercluster 或 Mapbox 内置聚类处理大数据集标记(100+)
- 店铺定位模式:地理编码用户位置 → 使用 Haversine/PostGIS 查询附近店铺 → 在地图上显示
- 路线显示:Mapbox Directions API 用于驾车/步行路线,渲染为 GeoJSON LineString 图层
- 浏览器地理定位:
navigator.geolocation.getCurrentPosition() — 始终优雅处理权限拒绝
- 惰性加载地图组件 — 地图较重;使用
dynamic(() => import("./Map"), { ssr: false })
模式
import Map, { Marker, NavigationControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
function StoreMap({ stores }: { stores: Store[] }) {
return (
<Map
initialViewState={{ longitude: -122.4, latitude: 37.8, zoom: 11 }}
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_TOKEN}
mapStyle="mapbox://styles/mapbox/streets-v12"
style={{ width: "100%", height: 400 }}
>
<NavigationControl position="top-right" />
{stores.map((store) => (
<Marker key={store.id} longitude={store.lng} latitude={store.lat}>
<Pin />
</Marker>
))}
</Map>
);
}
function haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371;
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a = Math.sin(dLat / 2) ** 2 + Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
避免
- 服务端渲染地图组件 — 始终使用
ssr: false 或 "use client" 加载
- 未经聚类直接渲染 1000+ 标记 — 会严重影响性能;请使用 supercluster
- 硬编码坐标 — 在构建时或按需对地址进行地理编码
- 忽略地理定位权限拒绝 — 始终提供回退方案(如手动输入地址)