react+Mapbox GL实现标记地点、地域的功能

[复制链接]
发表于 2025-5-25 02:48:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
准备工作

首先,确保你已经:

  • 注册了 Mapbox 账号并获取了访问令牌(access token)
  • 创建了 React 项目
安装必要的依赖:
  1. npm install mapbox-gl react-map-gl
  2. # 或者
  3. yarn add mapbox-gl react-map-gl
复制代码
基础舆图组件

首先创建一个基础舆图组件:
  1. import React, { useRef, useEffect, useState } from 'react';
  2. import mapboxgl from 'mapbox-gl';
  3. import 'mapbox-gl/dist/mapbox-gl.css';
  4. // 设置你的 Mapbox 访问令牌
  5. mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
  6. const MapboxMap = () => {
  7.   const mapContainer = useRef(null);
  8.   const map = useRef(null);
  9.   const [lng, setLng] = useState(-70.9);
  10.   const [lat, setLat] = useState(42.35);
  11.   const [zoom, setZoom] = useState(9);
  12.   useEffect(() => {
  13.     if (map.current) return; // 如果地图已经初始化,则不再重复初始化
  14.     map.current = new mapboxgl.Map({
  15.       container: mapContainer.current,
  16.       style: 'mapbox://styles/mapbox/streets-v11',
  17.       center: [lng, lat],
  18.       zoom: zoom
  19.     });
  20.     map.current.on('move', () => {
  21.       setLng(map.current.getCenter().lng.toFixed(4));
  22.       setLat(map.current.getCenter().lat.toFixed(4));
  23.       setZoom(map.current.getZoom().toFixed(2));
  24.     });
  25.   }, []);
  26.   return (
  27.     <div>
  28.       <div className="sidebar">
  29.         Longitude: {lng} | Latitude: {lat} | Zoom: {zoom}
  30.       </div>
  31.       <div ref={mapContainer} className="map-container" />
  32.     </div>
  33.   );
  34. };
  35. export default MapboxMap;
复制代码
添加点标记

要在舆图上添加点标记,可以使用 mapboxgl.Marker:
  1. useEffect(() => {
  2.   if (!map.current) return;
  3.   // 添加一个点标记
  4.   new mapboxgl.Marker()
  5.     .setLngLat([-70.9, 42.35])
  6.     .addTo(map.current);
  7.   // 可以添加多个标记
  8.   const locations = [
  9.     { lng: -70.92, lat: 42.36, title: '地点1' },
  10.     { lng: -70.88, lat: 42.34, title: '地点2' },
  11.   ];
  12.   locations.forEach(loc => {
  13.     new mapboxgl.Marker()
  14.       .setLngLat([loc.lng, loc.lat])
  15.       .setPopup(new mapboxgl.Popup().setHTML(`<h3>${loc.title}</h3>`))
  16.       .addTo(map.current);
  17.   });
  18. }, []);
复制代码
绘制地域(多边形)

要绘制多边形地域,我们必要使用 GeoJSON 数据:
  1. useEffect(() => {
  2.   if (!map.current) return;
  3.   // 等待地图加载完成
  4.   map.current.on('load', () => {
  5.     // 添加一个多边形区域
  6.     map.current.addLayer({
  7.       id: 'polygon',
  8.       type: 'fill',
  9.       source: {
  10.         type: 'geojson',
  11.         data: {
  12.           type: 'Feature',
  13.           geometry: {
  14.             type: 'Polygon',
  15.             coordinates: [[
  16.               [-70.92, 42.36],
  17.               [-70.88, 42.36],
  18.               [-70.88, 42.34],
  19.               [-70.92, 42.34],
  20.               [-70.92, 42.36]
  21.             ]]
  22.           },
  23.           properties: {}
  24.         }
  25.       },
  26.       paint: {
  27.         'fill-color': '#088',
  28.         'fill-opacity': 0.4,
  29.         'fill-outline-color': '#000'
  30.       }
  31.     });
  32.     // 添加可交互的多边形
  33.     map.current.addLayer({
  34.       id: 'interactive-polygon',
  35.       type: 'fill',
  36.       source: {
  37.         type: 'geojson',
  38.         data: {
  39.           type: 'Feature',
  40.           geometry: {
  41.             type: 'Polygon',
  42.             coordinates: [[
  43.               [-70.95, 42.38],
  44.               [-70.90, 42.38],
  45.               [-70.90, 42.33],
  46.               [-70.95, 42.33],
  47.               [-70.95, 42.38]
  48.             ]]
  49.           },
  50.           properties: {
  51.             name: '可交互区域'
  52.           }
  53.         }
  54.       },
  55.       paint: {
  56.         'fill-color': '#800',
  57.         'fill-opacity': 0.4,
  58.         'fill-outline-color': '#000'
  59.       }
  60.     });
  61.     // 添加点击事件
  62.     map.current.on('click', 'interactive-polygon', (e) => {
  63.       new mapboxgl.Popup()
  64.         .setLngLat(e.lngLat)
  65.         .setHTML(`<h3>${e.features[0].properties.name}</h3>`)
  66.         .addTo(map.current);
  67.     });
  68.     // 鼠标悬停效果
  69.     map.current.on('mouseenter', 'interactive-polygon', () => {
  70.       map.current.getCanvas().style.cursor = 'pointer';
  71.     });
  72.     map.current.on('mouseleave', 'interactive-polygon', () => {
  73.       map.current.getCanvas().style.cursor = '';
  74.     });
  75.   });
  76. }, []);
复制代码
完整示例

下面是一个完整的组件,结合了标记地点和绘制地域的功能
  1. import React, { useRef, useEffect, useState } from 'react';
  2. import mapboxgl from 'mapbox-gl';
  3. import 'mapbox-gl/dist/mapbox-gl.css';
  4. mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
  5. const MapWithMarkersAndPolygons = () => {
  6.   const mapContainer = useRef(null);
  7.   const map = useRef(null);
  8.   const [lng, setLng] = useState(-70.9);
  9.   const [lat, setLat] = useState(42.35);
  10.   const [zoom, setZoom] = useState(9);
  11.   useEffect(() => {
  12.     if (map.current) return;
  13.     map.current = new mapboxgl.Map({
  14.       container: mapContainer.current,
  15.       style: 'mapbox://styles/mapbox/streets-v11',
  16.       center: [lng, lat],
  17.       zoom: zoom
  18.     });
  19.     map.current.on('move', () => {
  20.       setLng(map.current.getCenter().lng.toFixed(4));
  21.       setLat(map.current.getCenter().lat.toFixed(4));
  22.       setZoom(map.current.getZoom().toFixed(2));
  23.     });
  24.     // 添加标记
  25.     map.current.on('load', () => {
  26.       // 点标记
  27.       const locations = [
  28.         { lng: -70.92, lat: 42.36, title: '地点1' },
  29.         { lng: -70.88, lat: 42.34, title: '地点2' },
  30.         { lng: -70.95, lat: 42.35, title: '地点3' }
  31.       ];
  32.       locations.forEach(loc => {
  33.         new mapboxgl.Marker()
  34.           .setLngLat([loc.lng, loc.lat])
  35.           .setPopup(new mapboxgl.Popup().setHTML(`<h3>${loc.title}</h3>`))
  36.           .addTo(map.current);
  37.       });
  38.       // 多边形区域
  39.       map.current.addLayer({
  40.         id: 'polygon',
  41.         type: 'fill',
  42.         source: {
  43.           type: 'geojson',
  44.           data: {
  45.             type: 'Feature',
  46.             geometry: {
  47.               type: 'Polygon',
  48.               coordinates: [[
  49.                 [-70.92, 42.36],
  50.                 [-70.88, 42.36],
  51.                 [-70.88, 42.34],
  52.                 [-70.92, 42.34],
  53.                 [-70.92, 42.36]
  54.               ]]
  55.             },
  56.             properties: {
  57.               description: '静态区域'
  58.             }
  59.           }
  60.         },
  61.         paint: {
  62.           'fill-color': '#088',
  63.           'fill-opacity': 0.4,
  64.           'fill-outline-color': '#000'
  65.         }
  66.       });
  67.       // 可交互多边形
  68.       map.current.addLayer({
  69.         id: 'interactive-polygon',
  70.         type: 'fill',
  71.         source: {
  72.           type: 'geojson',
  73.           data: {
  74.             type: 'Feature',
  75.             geometry: {
  76.               type: 'Polygon',
  77.               coordinates: [[
  78.                 [-70.95, 42.38],
  79.                 [-70.90, 42.38],
  80.                 [-70.90, 42.33],
  81.                 [-70.95, 42.33],
  82.                 [-70.95, 42.38]
  83.               ]]
  84.             },
  85.             properties: {
  86.               name: '可交互区域',
  87.               description: '点击我可以看到更多信息'
  88.             }
  89.           }
  90.         },
  91.         paint: {
  92.           'fill-color': '#800',
  93.           'fill-opacity': 0.4,
  94.           'fill-outline-color': '#000'
  95.         }
  96.       });
  97.       // 添加点击事件
  98.       map.current.on('click', 'interactive-polygon', (e) => {
  99.         new mapboxgl.Popup()
  100.           .setLngLat(e.lngLat)
  101.           .setHTML(`
  102.             <h3>${e.features[0].properties.name}</h3>
  103.             <p>${e.features[0].properties.description}</p>
  104.           `)
  105.           .addTo(map.current);
  106.       });
  107.       // 鼠标悬停效果
  108.       map.current.on('mouseenter', 'interactive-polygon', () => {
  109.         map.current.getCanvas().style.cursor = 'pointer';
  110.       });
  111.       map.current.on('mouseleave', 'interactive-polygon', () => {
  112.         map.current.getCanvas().style.cursor = '';
  113.       });
  114.     });
  115.   }, []);
  116.   return (
  117.     <div>
  118.       <div className="sidebar">
  119.         Longitude: {lng} | Latitude: {lat} | Zoom: {zoom}
  120.       </div>
  121.       <div ref={mapContainer} className="map-container" />
  122.     </div>
  123.   );
  124. };
  125. export default MapWithMarkersAndPolygons;
复制代码
样式

添加一些根本样式到你的 CSS 文件中:
  1. .map-container {
  2.   position: absolute;
  3.   top: 0;
  4.   bottom: 0;
  5.   left: 0;
  6.   right: 0;
  7. }
  8. .sidebar {
  9.   position: absolute;
  10.   top: 0;
  11.   left: 0;
  12.   margin: 12px;
  13.   background-color: #404040;
  14.   color: #ffffff;
  15.   z-index: 1;
  16.   padding: 6px;
  17.   font-weight: bold;
  18. }
复制代码
使用 react-map-gl (官方 React 封装)

如果你更喜好使用官方 React 封装,可以如许实现:
[code]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' }}>
继续阅读请点击广告
回复

使用道具 举报

快速回复 返回顶部 返回列表