MapBox GL地图上绘制圆形区域,在区域中心点添加标记点及文本提示的实现方法

MapBox GL地图上绘制圆形区域,在区域中心点添加标记点及文本提示的实现方法:

javascript 复制代码
// 绘制影响区域
const addArea = (circle) => {
  if (!map.current || !circle) return;

  const areaId = `circle-area`;
  const epicenterId = `circle-epicenter`;
  const radiusKm = circle.strongRadius;

  // 使用传入的中心点坐标
  const epicenter = [parseFloat(circle.longitude), parseFloat(circle.latitude)];

  // 清除旧图层
  clearLayers();

  // 创建圆形多边形
  const polygon = createCirclePolygon(epicenter, radiusKm);

  // 添加数据源
  map.current.addSource(areaId, {
    type: 'geojson',
    data: polygon
  });

  // 添加区域填充层
  map.current.addLayer({
    id: `${areaId}-fill`,
    type: 'fill',
    source: areaId,
    paint: {
      'fill-color': '#FF4D4F',
      'fill-opacity': 0.2
    }
  });

  // 添加区域边框层
  map.current.addLayer({
    id: `${areaId}-border`,
    type: 'line',
    source: areaId,
    paint: {
      'line-color': '#FF4D4F',
      'line-width': 3,
      'line-dasharray': [2, 2]
    }
  });

  // 创建文字元素并添加到地图
  const textElement = document.createElement('div');
  textElement.className = 'circle-text';
  textElement.style.cssText = `
    position: absolute;
    transform: translate(-50%, -100%);
    background: rgba(0, 0, 0, 0.9);
    color: white;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: bold;
    white-space: pre-line;
    text-align: center;
    max-width: 150px;
    word-break: break-word;
    z-index: 10;
    pointer-events: none;
    border: 2px solid #ff4d4f;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    line-height: 1.4;
  `;
  textElement.textContent = `覆盖半径为${radiusKm || 'XX'}公里`;

  // 将文字元素添加到地图容器
  const mapContainerEl = map.current.getContainer();
  mapContainerEl.appendChild(textElement);

  // 更新文字位置随地图移动
  const updateTextPosition = () => {
    const point = map.current.project(epicenter);
    textElement.style.left = `${point.x}px`;
    textElement.style.top = `${point.y}px`;
  };

  map.current.on('move', updateTextPosition);
  updateTextPosition();

  // 创建图标标记 - 只创建一个标记
  const iconElement = document.createElement('div');
  iconElement.className = 'circle-icon';
  iconElement.style.width = '40px';
  iconElement.style.height = '40px';
  iconElement.style.backgroundImage = `url(${require('@assets/images/map/earthquakeCenter.png')})`;
  iconElement.style.backgroundSize = 'contain';
  iconElement.style.backgroundRepeat = 'no-repeat';
  iconElement.style.cursor = 'pointer';
  iconElement.title = `覆盖半径为${radiusKm || 'XX'}公里`;

  const marker = new window.mapboxgl.Marker({
    element: iconElement,
    anchor: 'center'
  }).setLngLat(epicenter).addTo(map.current);

  // 管理标记状态
  setMarkers(prev => [...prev, {
    id: epicenterId,
    marker: marker,
    element: iconElement,
    type: 'circle',
    textElement: textElement,
    updateTextPosition: updateTextPosition
  }]);
};

// 清理图层
const clearLayers = () => {
  const areaId = 'circle-area';
  const epicenterId = 'circle-epicenter';

  if (map.current) {
    // 移除图层
    if (map.current.getLayer(`${areaId}-fill`)) map.current.removeLayer(`${areaId}-fill`);
    if (map.current.getLayer(`${areaId}-border`)) map.current.removeLayer(`${areaId}-border`);
    if (map.current.getSource(areaId)) map.current.removeSource(areaId);

    // 使用函数式更新来访问最新的markers状态
    setMarkers(prev => {
      // 在当前作用域内清理标记
      prev.forEach(marker => {
        if (marker.id === epicenterId) {
          if (marker.marker) {
            marker.marker.remove();
          }
          if (marker.textElement && marker.textElement.parentNode) {
            marker.textElement.parentNode.removeChild(marker.textElement);
          }
          if (marker.updateTextPosition) {
            map.current.off('move', marker.updateTextPosition);
          }
        }
      });
      
      // 从状态中移除标记
      return prev.filter(marker => marker.id !== epicenterId);
    });
  }
};

// 创建精确的圆形多边形(考虑投影变形)
  const createCirclePolygon = (center, radiusKm, points = 64) => {
    const coords = [];
    const latRad = center[1] * Math.PI / 180;

    // 计算经度和纬度的每公里度数
    const latKm = 1 / 110.574;
    const lngKm = 1 / (111.320 * Math.cos(latRad));

    for (let i = 0; i < points; i++) {
      const angle = (i / points) * 2 * Math.PI;
      const dx = radiusKm * Math.cos(angle) * lngKm;
      const dy = radiusKm * Math.sin(angle) * latKm;
      coords.push([center[0] + dx, center[1] + dy]);
    }

    // 闭合多边形
    coords.push(coords[0]);

    return {
      type: 'Feature',
      geometry: {
        type: 'Polygon',
        coordinates: [coords]
      },
      properties: {
        radius: radiusKm
      }
    };
  };

上述方法的调用:

javascript 复制代码
addArea({
  depth: 10,
  latitude: 40.62,
  magnitude: 3.55,
  strongRadius: 4.5,
  longitude: 115.369
});