React+MapBox GL JS引入URL服务地址实现自定义图标标记地点、区域绘制功能

实现说明

  1. 自定义图标配置

    • customIcons 对象中定义所有类型的图标URL

    • 支持不同类型的机房、基站和断点使用不同图标

  2. 标记创建函数

    • addCustomMarker 是一个通用函数,用于创建带有自定义图标的标记

    • 可以设置图标URL、标题和自定义CSS类

  3. 标记类型专用函数

    • addComputerRoomMarkers - 添加机房标记

    • addBaseStationMarkers - 添加基站标记

    • addBreakPointMarkers - 添加断点标记

  4. 图标样式控制

    • 通过CSS控制图标大小(32x32像素)

    • 使用 background-image 显示自定义图标

    • 添加了悬停提示(title属性)

javascript 复制代码
import React, { useState, useEffect, useRef } from 'react';

const MapComponent = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);
  const [mapLoaded, setMapLoaded] = useState(false);
  const [markers, setMarkers] = useState([]);
  const [legendVisible, setLegendVisible] = useState(true);
  const [visibleLayers, setVisibleLayers] = useState({
    computerRooms: true,
    baseStations: true,
    breakPoints: true
  });

  // 模拟图标URL - 实际使用时替换为您的真实图标URL
  const customIcons = {
    computerRoom: {
      core: 'https://cdn-icons-png.flaticon.com/512/149/149060.png', // 替换为您的核心机房图标
      edge: 'https://cdn-icons-png.flaticon.com/512/149/149025.png', // 替换为您的边缘机房图标
      access: 'https://cdn-icons-png.flaticon.com/512/149/149014.png' // 替换为您的接入机房图标
    },
    baseStation: {
      '5g': 'https://cdn-icons-png.flaticon.com/512/4618/4618295.png', // 5G基站图标
      '4g': 'https://cdn-icons-png.flaticon.com/512/4618/4618286.png', // 4G基站图标
      '3g': 'https://cdn-icons-png.flaticon.com/512/4618/4618275.png'  // 3G基站图标
    },
    breakPoint: 'https://cdn-icons-png.flaticon.com/512/1828/1828665.png' // 断点图标
  };

  // 动态加载 Mapbox GL JS
  useEffect(() => {
    if (window.mapboxgl) {
      setMapLoaded(true);
      return;
    }

    const script = document.createElement('script');
    script.src = 'https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.js';
    script.onload = () => {
      const link = document.createElement('link');
      link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css';
      link.rel = 'stylesheet';
      document.head.appendChild(link);
      setMapLoaded(true);
    };
    script.onerror = () => console.error('Failed to load Mapbox GL JS');
    document.head.appendChild(script);

    return () => {
      document.head.removeChild(script);
    };
  }, []);

  // 添加带有自定义图标的标记
  const addCustomMarker = (lnglat, iconUrl, title, className = '') => {
    if (!map.current) return null;

    const el = document.createElement('div');
    el.className = `custom-marker ${className}`;
    el.style.width = '32px';
    el.style.height = '32px';
    el.style.backgroundImage = `url(${iconUrl})`;
    el.style.backgroundSize = 'contain';
    el.style.backgroundRepeat = 'no-repeat';
    el.style.cursor = 'pointer';
    if (title) el.title = title;

    const marker = new window.mapboxgl.Marker({
      element: el,
      anchor: 'bottom' // 图标底部对准坐标点
    })
      .setLngLat(lnglat)
      .addTo(map.current);

    return marker;
  };

  // 添加机房标记
  const addComputerRoomMarkers = (rooms) => {
    if (!map.current) return;

    const newMarkers = rooms.map(room => {
      const marker = addCustomMarker(
        [room.lng, room.lat],
        customIcons.computerRoom[room.type],
        room.name,
        `computer-room ${room.type}`
      );

      return { 
        id: room.id, 
        marker, 
        type: 'computerRoom', 
        originalType: room.type 
      };
    });

    setMarkers(prev => [...prev, ...newMarkers]);
  };

  // 添加基站标记
  const addBaseStationMarkers = (stations) => {
    if (!map.current) return;

    const newStations = stations.map(station => {
      const marker = addCustomMarker(
        [station.lng, station.lat],
        customIcons.baseStation[station.type],
        `${station.name} (${station.type.toUpperCase()})`,
        `base-station ${station.type}`
      );

      return { 
        id: station.id, 
        marker, 
        type: 'baseStation', 
        originalType: station.type 
      };
    });

    setMarkers(prev => [...prev, ...newStations]);
  };

  // 添加断点标记
  const addBreakPointMarkers = (points) => {
    if (!map.current) return;

    const newMarkers = points.map(point => {
      const marker = addCustomMarker(
        [point.lng, point.lat],
        customIcons.breakPoint,
        point.title,
        'break-point'
      );

      return { id: point.id, marker, type: 'breakPoint' };
    });

    setMarkers(prev => [...prev, ...newMarkers]);
  };

  // 切换图层可见性
  const toggleLayerVisibility = (layerType) => {
    const newVisibility = !visibleLayers[layerType];
    setVisibleLayers(prev => ({ ...prev, [layerType]: newVisibility }));

    markers.forEach(marker => {
      if (marker.type === layerType || 
          (layerType === 'computerRooms' && marker.type === 'computerRoom') ||
          (layerType === 'baseStations' && marker.type === 'baseStation') ||
          (layerType === 'breakPoints' && marker.type === 'breakPoint')) {
        const element = marker.marker.getElement();
        if (element) {
          element.style.display = newVisibility ? 'block' : 'none';
        }
      }
    });
  };

  // 初始化地图
  useEffect(() => {
    if (!mapLoaded || !mapContainer.current) return;

    map.current = new window.mapboxgl.Map({
      container: mapContainer.current,
      style: 'mapbox://styles/mapbox/streets-v11', // 使用默认地图样式
      center: [111.62299, 40.80772],
      zoom: 10,
      minZoom: 7,
      maxZoom: 19
    });

    map.current.on('load', () => {
      // 添加机房标记
      const computerRooms = [
        { id: 1, name: '核心机房', lng: 111.62, lat: 40.81, type: 'core' },
        { id: 2, name: '边缘机房', lng: 111.63, lat: 40.80, type: 'edge' },
        { id: 3, name: '接入机房', lng: 111.61, lat: 40.805, type: 'access' }
      ];
      addComputerRoomMarkers(computerRooms);

      // 添加基站标记
      const baseStations = [
        { id: 101, name: '5G基站A', lng: 111.615, lat: 40.808, type: '5g' },
        { id: 102, name: '4G基站B', lng: 111.625, lat: 40.806, type: '4g' },
        { id: 103, name: '3G基站C', lng: 111.635, lat: 40.804, type: '3g' }
      ];
      addBaseStationMarkers(baseStations);

      // 添加断点标记
      const breakPoints = [
        { id: 'bp1', lng: 111.625, lat: 40.806, title: '光缆断点1' },
        { id: 'bp2', lng: 111.635, lat: 40.8045, title: '光缆断点2' }
      ];
      addBreakPointMarkers(breakPoints);
    });

    return () => {
      if (map.current) {
        map.current.remove();
      }
    };
  }, [mapLoaded]);

  if (!mapLoaded) {
    return <div style={{
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      height: '100vh',
      fontSize: '18px',
      color: '#666'
    }}>正在加载地图资源...</div>;
  }

  return (
    <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
      <div ref={mapContainer} style={{ width: '100%', height: '100%' }} />
      
      {/* 图例悬浮框 */}
      {legendVisible && (
        <div style={{
          position: 'absolute',
          bottom: '20px',
          right: '20px',
          backgroundColor: 'white',
          padding: '15px',
          borderRadius: '5px',
          boxShadow: '0 0 10px rgba(0,0,0,0.2)',
          zIndex: 1,
          maxWidth: '250px'
        }}>
          <h3 style={{ marginTop: 0, marginBottom: '15px' }}>地图图例</h3>
          
          {/* 机房图例 */}
          <div style={{ marginBottom: '15px' }}>
            <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', marginBottom: '5px' }}>
              <input 
                type="checkbox" 
                checked={visibleLayers.computerRooms}
                onChange={() => toggleLayerVisibility('computerRooms')}
                style={{ marginRight: '8px' }}
              />
              <span>显示机房</span>
            </label>
            <div style={{ marginLeft: '25px' }}>
              <div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
                <img src={customIcons.computerRoom.core} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="核心机房" />
                <span>核心机房</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
                <img src={customIcons.computerRoom.edge} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="边缘机房" />
                <span>边缘机房</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                <img src={customIcons.computerRoom.access} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="接入机房" />
                <span>接入机房</span>
              </div>
            </div>
          </div>
          
          {/* 基站图例 */}
          <div style={{ marginBottom: '15px' }}>
            <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', marginBottom: '5px' }}>
              <input 
                type="checkbox" 
                checked={visibleLayers.baseStations}
                onChange={() => toggleLayerVisibility('baseStations')}
                style={{ marginRight: '8px' }}
              />
              <span>显示基站</span>
            </label>
            <div style={{ marginLeft: '25px' }}>
              <div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
                <img src={customIcons.baseStation['5g']} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="5G基站" />
                <span>5G基站</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', marginBottom: '3px' }}>
                <img src={customIcons.baseStation['4g']} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="4G基站" />
                <span>4G基站</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                <img src={customIcons.baseStation['3g']} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="3G基站" />
                <span>3G基站</span>
              </div>
            </div>
          </div>
          
          {/* 断点图例 */}
          <div>
            <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', marginBottom: '5px' }}>
              <input 
                type="checkbox" 
                checked={visibleLayers.breakPoints}
                onChange={() => toggleLayerVisibility('breakPoints')}
                style={{ marginRight: '8px' }}
              />
              <span>显示断点</span>
            </label>
            <div style={{ marginLeft: '25px' }}>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                <img src={customIcons.breakPoint} style={{ width: '20px', height: '20px', marginRight: '8px' }} alt="断点" />
                <span>光缆断点</span>
              </div>
            </div>
          </div>
        </div>
      )}
      
      {/* 显示/隐藏图例按钮 */}
      <button 
        onClick={() => setLegendVisible(!legendVisible)}
        style={{
          position: 'absolute',
          bottom: '20px',
          right: legendVisible ? '280px' : '20px',
          zIndex: 1,
          padding: '6px 12px',
          backgroundColor: 'white',
          border: '1px solid #ccc',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '14px',
          boxShadow: '0 0 5px rgba(0,0,0,0.1)'
        }}
      >
        {legendVisible ? '隐藏图例' : '显示图例'}
      </button>
    </div>
  );
};

export default MapComponent;
相关推荐
不思念一个荒废的名字2 分钟前
【黑马JavaWeb+AI知识梳理】后端Web基础03 - MySQL概述
前端·数据库·mysql
谢尔登1 小时前
【React Native】快速入门
javascript·react native·react.js
进取星辰1 小时前
32、跨平台咒语—— React Native初探
javascript·react native·react.js
橙子199110161 小时前
谈谈 Kotlin 中的构造方法,有哪些注意事项?
java·前端·kotlin
*neverGiveUp*2 小时前
本地分支git push 报错 fatal: The current branch XXXX has no upstream branch.
前端·git·gitea
AaronZZH2 小时前
为什么现代CSS应该选择OKLCH:从颜色科学到设计系统革新
前端·css
CaseyWei2 小时前
JS实现直接下载PDF文件
前端·javascript
球球和皮皮2 小时前
Babylon.js学习之路《七、用户交互:鼠标点击、拖拽与射线检测》
javascript·3d·前端框架·babylon.js
pianmian13 小时前
3dczml时间动态图型场景
前端·javascript·数据库
编程大全3 小时前
45道工程模块化高频题整理(附答案背诵版)
前端·工程化