You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ReactJS的Mapbox地图中绘制可动态调整的半圆?

在React + Mapbox中绘制动态角度半圆的实现方案

核心思路

借助Mapbox的GeoJSON Source和fill图层,通过计算半圆的地理坐标点生成Polygon几何图形,结合React状态控制图层显隐,完全适配动态角度、固定半径的需求,无需依赖图片资源。

实现步骤

1. 生成半圆的GeoJSON坐标

编写函数,根据中心经纬度、半径(米)、起始/结束角度,生成闭合的Polygon坐标集合。可以用Turf.js简化坐标计算,也可自行实现球面坐标转换逻辑。

方案一:使用Turf.js(推荐)

// 先安装依赖:npm install @turf/turf
import { destination, point } from '@turf/turf';

// 生成半圆的GeoJSON Feature
const createSemicircleGeoJSON = (centerLng, centerLat, radiusMeters, startAngle, endAngle) => {
  const points = [];
  // 先添加中心点,用于闭合Polygon
  points.push([centerLng, centerLat]);

  // 角度步长,值越小半圆边缘越平滑
  const step = 1;
  let currentAngle = startAngle;

  // 循环计算半圆上的每个点
  while (Math.abs(currentAngle - endAngle) > step) {
    // Turf.destination:从中心点沿指定角度(正北为0度,顺时针递增)移动指定距离
    const destPoint = destination(point([centerLng, centerLat]), radiusMeters / 1000, currentAngle, { units: 'kilometers' });
    points.push(destPoint.geometry.coordinates);
    currentAngle = (currentAngle + step) % 360;
  }

  // 添加结束点和中心点,完成闭合
  const endPoint = destination(point([centerLng, centerLat]), radiusMeters / 1000, endAngle, { units: 'kilometers' });
  points.push(endPoint.geometry.coordinates);
  points.push([centerLng, centerLat]);

  return {
    type: 'Feature',
    properties: {},
    geometry: { type: 'Polygon', coordinates: [points] }
  };
};

方案二:无依赖实现坐标计算

如果不想引入Turf,可自行实现球面坐标转换:

const degreesToRadians = (deg) => deg * Math.PI / 180;
const radiansToDegrees = (rad) => rad * 180 / Math.PI;

// 计算指定角度和距离的目标坐标
const getDestinationPoint = (lat, lng, distanceMeters, angleDeg) => {
  const earthRadius = 6378137; // 地球半径(米)
  const latRad = degreesToRadians(lat);
  const lngRad = degreesToRadians(lng);
  const angleRad = degreesToRadians(angleDeg);

  const newLatRad = Math.asin(
    Math.sin(latRad) * Math.cos(distanceMeters / earthRadius) +
    Math.cos(latRad) * Math.sin(distanceMeters / earthRadius) * Math.cos(angleRad)
  );
  const newLngRad = lngRad + Math.atan2(
    Math.sin(angleRad) * Math.sin(distanceMeters / earthRadius) * Math.cos(latRad),
    Math.cos(distanceMeters / earthRadius) - Math.sin(latRad) * Math.sin(newLatRad)
  );

  return [radiansToDegrees(newLngRad), radiansToDegrees(newLatRad)];
};

// 基于上述函数生成半圆GeoJSON
const createSemicircleGeoJSON = (centerLng, centerLat, radiusMeters, startAngle, endAngle) => {
  const points = [[centerLng, centerLat]];
  const step = 1;
  let currentAngle = startAngle;

  while (Math.abs(currentAngle - endAngle) > step) {
    points.push(getDestinationPoint(centerLat, centerLng, radiusMeters, currentAngle));
    currentAngle = (currentAngle + step) % 360;
  }

  points.push(getDestinationPoint(centerLat, centerLng, radiusMeters, endAngle));
  points.push([centerLng, centerLat]);

  return {
    type: 'Feature',
    properties: {},
    geometry: { type: 'Polygon', coordinates: [points] }
  };
};

2. React组件中集成Mapbox渲染半圆

结合React状态管理,控制半圆的显示/隐藏、动态更新角度和半径:

import React, { useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import { createSemicircleGeoJSON } from './utils';

mapboxgl.accessToken = '你的Mapbox Token';

const MapWithSemicircle = () => {
  const mapContainerRef = useRef(null);
  const mapRef = useRef(null);
  // 设备状态:控制半圆显隐
  const [deviceActive, setDeviceActive] = useState(false);
  // 从服务器获取的固定半径(示例值)
  const [radius, setRadius] = useState(500);
  // 动态角度范围(示例值:从正东到正西)
  const [angleRange, setAngleRange] = useState({ start: 90, end: 270 });
  // 半圆中心点坐标(可从服务器获取)
  const [center, setCenter] = useState([116.3972, 39.9075]);

  // 初始化地图
  useEffect(() => {
    if (!mapRef.current) {
      mapRef.current = new mapboxgl.Map({
        container: mapContainerRef.current,
        style: 'mapbox://styles/mapbox/light-v11', // 替换为你的基础样式,需包含浅蓝色底图
        center: center,
        zoom: 15
      });
    }

    const map = mapRef.current;
    map.on('load', () => {
      // 添加半圆的数据源
      map.addSource('semicircle-source', {
        type: 'geojson',
        data: createSemicircleGeoJSON(center[0], center[1], radius, angleRange.start, angleRange.end)
      });

      // 添加半圆填充图层,设置深蓝色,叠加在浅蓝色区域上
      map.addLayer({
        id: 'semicircle-layer',
        type: 'fill',
        source: 'semicircle-source',
        paint: {
          'fill-color': '#003366', // 深蓝色
          'fill-opacity': 0.7 // 调整透明度确保叠加可见
        },
        layout: {
          visibility: deviceActive ? 'visible' : 'none'
        }
      });

      // 若需控制图层叠加顺序,可添加beforeId参数:beforeId: '你的浅蓝色图层ID'
    });

    // 清理函数
    return () => {
      mapRef.current?.remove();
      mapRef.current = null;
    };
  }, []);

  // 监听状态变化,更新半圆数据和显隐
  useEffect(() => {
    const map = mapRef.current;
    if (!map || !map.getSource('semicircle-source')) return;

    // 更新半圆坐标数据
    map.getSource('semicircle-source').setData(
      createSemicircleGeoJSON(center[0], center[1], radius, angleRange.start, angleRange.end)
    );

    // 更新图层可见性
    map.setLayoutProperty('semicircle-layer', 'visibility', deviceActive ? 'visible' : 'none');
  }, [deviceActive, radius, angleRange, center]);

  return (
    <div>
      <div ref={mapContainerRef} style={{ width: '100%', height: '600px' }} />
      {/* 示例控制按钮,模拟设备开关 */}
      <button onClick={() => setDeviceActive(!deviceActive)}>
        {deviceActive ? '关闭设备' : '开启设备'}
      </button>
      {/* 示例角度控制,模拟动态角度更新 */}
      <div>
        起始角度:<input
          type="number"
          value={angleRange.start}
          onChange={(e) => setAngleRange(prev => ({ ...prev, start: parseInt(e.target.value) }))}
          min={0}
          max={359}
        />
        结束角度:<input
          type="number"
          value={angleRange.end}
          onChange={(e) => setAngleRange(prev => ({ ...prev, end: parseInt(e.target.value) }))}
          min={0}
          max={359}
        />
      </div>
    </div>
  );
};

export default MapWithSemicircle;

3. 关键细节说明

  • 角度基准:Mapbox和Turf的角度以正北为0度,顺时针递增。若你的业务角度基准不同(比如正东为0),可通过angle = (inputAngle + 90) % 360转换。
  • 性能优化:若角度更新频繁,可增大步长(比如5度)减少坐标点数量,提升渲染效率。
  • 图层叠加:若浅蓝色区域是独立图层,可通过addLayer的beforeId参数,将半圆图层放在它上方。

内容的提问来源于stack exchange,提问作者Eduardo-Maia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 11:35:29