地圖與地理定位 是做什麼的?
規則
- Mapbox GL JS 用於豐富的互動式地圖 —
react-map-gl 作為 React 包裝器
- Leaflet 作為免費替代方案 —
react-leaflet 用於 React,基本圖磚無需 API 金鑰
- API 金鑰管理:客戶端 Mapbox 使用
NEXT_PUBLIC_MAPBOX_TOKEN,絕不暴露伺服器端秘密
- 地理編碼:將地址轉換為座標 — Mapbox Geocoding API 或 OpenStreetMap Nominatim(免費)
- 標記聚類:對大量標記資料集(100+)使用
supercluster 或 Mapbox 內建聚類
- 商店定位器模式:對使用者位置進行地理編碼 → 使用 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
- 硬編碼座標 — 在建置時或按需進行地理編碼
- 忽略地理定位權限拒絕 — 始終提供備選方案(手動輸入地址)