准备工作
首先,确保你已经:
注册了 Mapbox 账号并获取了访问令牌(access token)
创建了 React 项目
安装必要的依赖:
npm install mapbox-gl react-map-gl
# 或者
yarn add mapbox-gl react-map-gl
基础地图组件
首先创建一个基础地图组件:
import React, { useRef, useEffect, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
// 设置你的 Mapbox 访问令牌
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const MapboxMap = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [lng, setLng] = useState(-70.9);
const [lat, setLat] = useState(42.35);
const [zoom, setZoom] = useState(9);
useEffect(() => {
if (map.current) return; // 如果地图已经初始化,则不再重复初始化
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v11',
center: [lng, lat],
zoom: zoom
});
map.current.on('move', () => {
setLng(map.current.getCenter().lng.toFixed(4));
setLat(map.current.getCenter().lat.toFixed(4));
setZoom(map.current.getZoom().toFixed(2));
});
}, []);
return (
<div>
<div className="sidebar">
Longitude: {lng} | Latitude: {lat} | Zoom: {zoom}
</div>
<div ref={mapContainer} className="map-container" />
</div>
);
};
export default MapboxMap;
添加点标记
要在地图上添加点标记,可以使用 mapboxgl.Marker
:
useEffect(() => {
if (!map.current) return;
// 添加一个点标记
new mapboxgl.Marker()
.setLngLat([-70.9, 42.35])
.addTo(map.current);
// 可以添加多个标记
const locations = [
{ lng: -70.92, lat: 42.36, title: '地点1' },
{ lng: -70.88, lat: 42.34, title: '地点2' },
];
locations.forEach(loc => {
new mapboxgl.Marker()
.setLngLat([loc.lng, loc.lat])
.setPopup(new mapboxgl.Popup().setHTML(`<h3>${loc.title}</h3>`))
.addTo(map.current);
});
}, []);
绘制区域(多边形)
要绘制多边形区域,我们需要使用 GeoJSON 数据:
useEffect(() => {
if (!map.current) return;
// 等待地图加载完成
map.current.on('load', () => {
// 添加一个多边形区域
map.current.addLayer({
id: 'polygon',
type: 'fill',
source: {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[-70.92, 42.36],
[-70.88, 42.36],
[-70.88, 42.34],
[-70.92, 42.34],
[-70.92, 42.36]
]]
},
properties: {}
}
},
paint: {
'fill-color': '#088',
'fill-opacity': 0.4,
'fill-outline-color': '#000'
}
});
// 添加可交互的多边形
map.current.addLayer({
id: 'interactive-polygon',
type: 'fill',
source: {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[-70.95, 42.38],
[-70.90, 42.38],
[-70.90, 42.33],
[-70.95, 42.33],
[-70.95, 42.38]
]]
},
properties: {
name: '可交互区域'
}
}
},
paint: {
'fill-color': '#800',
'fill-opacity': 0.4,
'fill-outline-color': '#000'
}
});
// 添加点击事件
map.current.on('click', 'interactive-polygon', (e) => {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<h3>${e.features[0].properties.name}</h3>`)
.addTo(map.current);
});
// 鼠标悬停效果
map.current.on('mouseenter', 'interactive-polygon', () => {
map.current.getCanvas().style.cursor = 'pointer';
});
map.current.on('mouseleave', 'interactive-polygon', () => {
map.current.getCanvas().style.cursor = '';
});
});
}, []);
完整示例
下面是一个完整的组件,结合了标记地点和绘制区域的功能:
import React, { useRef, useEffect, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const MapWithMarkersAndPolygons = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [lng, setLng] = useState(-70.9);
const [lat, setLat] = useState(42.35);
const [zoom, setZoom] = useState(9);
useEffect(() => {
if (map.current) return;
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v11',
center: [lng, lat],
zoom: zoom
});
map.current.on('move', () => {
setLng(map.current.getCenter().lng.toFixed(4));
setLat(map.current.getCenter().lat.toFixed(4));
setZoom(map.current.getZoom().toFixed(2));
});
// 添加标记
map.current.on('load', () => {
// 点标记
const locations = [
{ lng: -70.92, lat: 42.36, title: '地点1' },
{ lng: -70.88, lat: 42.34, title: '地点2' },
{ lng: -70.95, lat: 42.35, title: '地点3' }
];
locations.forEach(loc => {
new mapboxgl.Marker()
.setLngLat([loc.lng, loc.lat])
.setPopup(new mapboxgl.Popup().setHTML(`<h3>${loc.title}</h3>`))
.addTo(map.current);
});
// 多边形区域
map.current.addLayer({
id: 'polygon',
type: 'fill',
source: {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[-70.92, 42.36],
[-70.88, 42.36],
[-70.88, 42.34],
[-70.92, 42.34],
[-70.92, 42.36]
]]
},
properties: {
description: '静态区域'
}
}
},
paint: {
'fill-color': '#088',
'fill-opacity': 0.4,
'fill-outline-color': '#000'
}
});
// 可交互多边形
map.current.addLayer({
id: 'interactive-polygon',
type: 'fill',
source: {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[-70.95, 42.38],
[-70.90, 42.38],
[-70.90, 42.33],
[-70.95, 42.33],
[-70.95, 42.38]
]]
},
properties: {
name: '可交互区域',
description: '点击我可以看到更多信息'
}
}
},
paint: {
'fill-color': '#800',
'fill-opacity': 0.4,
'fill-outline-color': '#000'
}
});
// 添加点击事件
map.current.on('click', 'interactive-polygon', (e) => {
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`
<h3>${e.features[0].properties.name}</h3>
<p>${e.features[0].properties.description}</p>
`)
.addTo(map.current);
});
// 鼠标悬停效果
map.current.on('mouseenter', 'interactive-polygon', () => {
map.current.getCanvas().style.cursor = 'pointer';
});
map.current.on('mouseleave', 'interactive-polygon', () => {
map.current.getCanvas().style.cursor = '';
});
});
}, []);
return (
<div>
<div className="sidebar">
Longitude: {lng} | Latitude: {lat} | Zoom: {zoom}
</div>
<div ref={mapContainer} className="map-container" />
</div>
);
};
export default MapWithMarkersAndPolygons;
样式
添加一些基本样式到你的 CSS 文件中:
.map-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.sidebar {
position: absolute;
top: 0;
left: 0;
margin: 12px;
background-color: #404040;
color: #ffffff;
z-index: 1;
padding: 6px;
font-weight: bold;
}
使用 react-map-gl (官方 React 封装)
如果你更喜欢使用官方 React 封装,可以这样实现:
import React, { useState, useCallback } from 'react';
import ReactMapGL, { Marker, Popup, Source, Layer } from 'react-map-gl';
const MapWithReactMapGL = () => {
const [viewport, setViewport] = useState({
latitude: 42.35,
longitude: -70.9,
zoom: 9,
width: '100%',
height: '100%'
});
const [selectedPoint, setSelectedPoint] = useState(null);
const [selectedPolygon, setSelectedPolygon] = useState(null);
const points = [
{ id: 1, longitude: -70.92, latitude: 42.36, name: '地点1' },
{ id: 2, longitude: -70.88, latitude: 42.34, name: '地点2' },
];
const polygonData = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[
[-70.92, 42.36],
[-70.88, 42.36],
[-70.88, 42.34],
[-70.92, 42.34],
[-70.92, 42.36]
]]
},
properties: {}
};
const handlePolygonClick = useCallback((event) => {
setSelectedPolygon({
lngLat: event.lngLat,
feature: event.features[0]
});
}, []);
return (
<ReactMapGL
{...viewport}
mapboxApiAccessToken="YOUR_MAPBOX_ACCESS_TOKEN"
onViewportChange={setViewport}
onClick={handlePolygonClick}
interactiveLayerIds={['polygon-layer']}
>
{/* 点标记 */}
{points.map(point => (
<Marker
key={point.id}
longitude={point.longitude}
latitude={point.latitude}
>
<button
style={{ background: 'none', border: 'none', cursor: 'pointer' }}
onClick={e => {
e.preventDefault();
setSelectedPoint(point);
}}
>
<div style={{ color: 'red', fontSize: '24px' }}>📍</div>
</button>
</Marker>
))}
{/* 点标记弹出框 */}
{selectedPoint && (
<Popup
longitude={selectedPoint.longitude}
latitude={selectedPoint.latitude}
onClose={() => setSelectedPoint(null)}
>
<div>
<h3>{selectedPoint.name}</h3>
<p>详细信息...</p>
</div>
</Popup>
)}
{/* 多边形区域 */}
<Source id="polygon-source" type="geojson" data={polygonData}>
<Layer
id="polygon-layer"
type="fill"
paint={{
'fill-color': '#088',
'fill-opacity': 0.4,
'fill-outline-color': '#000'
}}
/>
</Source>
{/* 多边形弹出框 */}
{selectedPolygon && (
<Popup
longitude={selectedPolygon.lngLat[0]}
latitude={selectedPolygon.lngLat[1]}
onClose={() => setSelectedPolygon(null)}
>
<div>
<h3>多边形区域</h3>
<p>点击了多边形</p>
</div>
</Popup>
)}
</ReactMapGL>
);
};
export default MapWithReactMapGL;