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

发布于:2025-05-25 ⋅ 阅读:(21) ⋅ 点赞:(0)

准备工作

首先,确保你已经:

  1. 注册了 Mapbox 账号并获取了访问令牌(access token)

  2. 创建了 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;


网站公告

今日签到

点亮在社区的每一天
去签到