react+Mapbox GL实现标记地点、区域的功能
准备工作
首先,确保你已经:
-
注册了 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 => (<Markerkey={point.id}longitude={point.longitude}latitude={point.latitude}><buttonstyle={{ background: 'none', border: 'none', cursor: 'pointer' }}onClick={e => {e.preventDefault();setSelectedPoint(point);}}><div style={{ color: 'red', fontSize: '24px' }}>📍</div></button></Marker>))}{/* 点标记弹出框 */}{selectedPoint && (<Popuplongitude={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}><Layerid="polygon-layer"type="fill"paint={{'fill-color': '#088','fill-opacity': 0.4,'fill-outline-color': '#000'}}/></Source>{/* 多边形弹出框 */}{selectedPolygon && (<Popuplongitude={selectedPolygon.lngLat[0]}latitude={selectedPolygon.lngLat[1]}onClose={() => setSelectedPolygon(null)}><div><h3>多边形区域</h3><p>点击了多边形</p></div></Popup>)}</ReactMapGL>);
};export default MapWithReactMapGL;