VUE3+Mapbox-GL 实现鼠标绘制矩形功能的详细代码和讲解

发布于:2025-04-02 ⋅ 阅读:(24) ⋅ 点赞:(0)

以下是如何使用 Mapbox GL JS 实现鼠标绘制矩形功能的详细代码和讲解。Mapbox GL JS 是一个强大的 JavaScript 库,可以用来创建交互式地图。下面将通过监听鼠标事件并动态更新地图图层来实现这一功能。


实现步骤

  1. 初始化地图

    • 在 HTML 文件中引入 Mapbox GL JS 库,并设置一个容器来显示地图。
    • 创建一个 mapboxgl.Map 实例,配置地图样式、中心点和缩放级别。
  2. 监听鼠标事件

    • 使用 map.on 方法监听 mousedown(鼠标按下)、mousemove(鼠标移动)和 mouseup(鼠标松开)事件。
    • mousedown 时记录矩形的起始点。
    • mousemove 时根据鼠标位置实时更新矩形。
    • mouseup 时结束绘制并固定矩形。
  3. 绘制矩形

    • 使用 GeoJSON 数据格式表示矩形。
    • 通过 map.addSourcemap.addLayer 将矩形添加到地图上。
    • 在鼠标移动时动态更新 GeoJSON 数据以显示矩形的当前形状。
  4. 结束绘制

    • 在绘制完成后移除不必要的事件监听器,避免重复触发。

详细代码

以下是完整的 HTML 和 JavaScript 代码示例:

<<template>
  <div class="map-container">
    <div id="map" ref="mapContainer"></div>
    <div class="control-panel">
      <button @click="toggleDrawMode">{{ isDrawMode ? '停止绘制' : '开始绘制' }}</button>
      <button @click="clearRectangles">清除所有</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
import { setMapInstance } from '@/utils/mapUtils'

const mapContainer = ref<HTMLElement | null>(null)
let map: mapboxgl.Map | null = null
const isDrawMode = ref(false)
const isDrawing = ref(false)
const startPoint = ref<[number, number] | null>(null)
const currentRectangle = ref<GeoJSON.Feature | null>(null)
const rectangleCollection = ref<GeoJSON.FeatureCollection>({
  type: 'FeatureCollection',
  features: []
})
const rectangleLayerId = 'rectangle-layer'
const tempRectangleSourceId = 'temp-rectangle-source'
const tempRectangleLayerId = 'temp-rectangle-layer'

const initMap = () => {
  if (!mapContainer.value) return
  
  mapboxgl.accessToken = "你的token"
  
  map = new mapboxgl.Map({
    container: mapContainer.value,
    style: 'mapbox://styles/mapbox/streets-v12',
    center: [116.397428, 39.90923],
    zoom: 12
  })

  map.on('load', () => {
    // Add source for completed rectangles
    map?.addSource('rectangle-source', {
      type: 'geojson',
      data: rectangleCollection.value
    })

    // Add layer for completed rectangles
    map?.addLayer({
      id: rectangleLayerId,
      type: 'fill',
      source: 'rectangle-source',
      paint: {
        'fill-color': '#4e9af5',
        'fill-opacity': 0.5,
        'fill-outline-color': '#0066cc'
      }
    })

    // Add layer for rectangle outline
    map?.addLayer({
      id: 'rectangle-outline',
      type: 'line',
      source: 'rectangle-source',
      paint: {
        'line-color': '#0066cc',
        'line-width': 2
      }
    })

    // Add source for the temp rectangle being drawn
    map?.addSource(tempRectangleSourceId, {
      type: 'geojson',
      data: {
        type: 'Feature',
        geometry: {
          type: 'Polygon',
          coordinates: [[
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0]
          ]]
        },
        properties: {}
      }
    })

    // Add layer for the temp rectangle
    map?.addLayer({
      id: tempRectangleLayerId,
      type: 'fill',
      source: tempRectangleSourceId,
      paint: {
        'fill-color': '#4e9af5',
        'fill-opacity': 0.3,
        'fill-outline-color': '#0066cc'
      }
    })

    // Add temp rectangle outline layer
    map?.addLayer({
      id: 'temp-rectangle-outline',
      type: 'line',
      source: tempRectangleSourceId,
      paint: {
        'line-color': '#0066cc',
        'line-width': 2,
        'line-dasharray': [2, 2]
      }
    })

    // Event handlers for drawing
    setupDrawingEventHandlers()
  })

  setMapInstance(map)
}

const setupDrawingEventHandlers = () => {
  if (!map) return

  // First click - start drawing
  map.on('click', (e) => {
    if (!isDrawMode.value) return
    
    // If not drawing yet, start a new rectangle
    if (!isDrawing.value) {
      isDrawing.value = true
      startPoint.value = [e.lngLat.lng, e.lngLat.lat]
      
      // Initialize the temp rectangle
      updateTempRectangle(startPoint.value, startPoint.value)
    } 
    // If already drawing, complete the rectangle
    else if (startPoint.value) {
      const endPoint: [number, number] = [e.lngLat.lng, e.lngLat.lat]
      
      // Complete the rectangle
      completeRectangle(startPoint.value, endPoint)
      
      // Reset drawing state
      isDrawing.value = false
      startPoint.value = null
      
      // Clear temp rectangle
      updateTempRectangle([0, 0], [0, 0])
    }
  })

  // Mouse move - update rectangle while in drawing mode, but after first click
  map.on('mousemove', (e) => {
    // Only update if we're in drawing mode AND we've made the first click
    if (!isDrawMode.value || !isDrawing.value || !startPoint.value) return
    
    // Update the rectangle as mouse moves (without needing to hold the button)
    updateTempRectangle(startPoint.value, [e.lngLat.lng, e.lngLat.lat])
  })
}

const updateTempRectangle = (start: [number, number], end: [number, number]) => {
  if (!map) return
  
  // Create rectangle coordinates from start and end points
  const coords = createRectangleCoordinates(start, end)
  
  // Update the temporary rectangle
  const geojsonSource = map.getSource(tempRectangleSourceId) as mapboxgl.GeoJSONSource
  if (geojsonSource) {
    geojsonSource.setData({
      type: 'Feature',
      geometry: {
        type: 'Polygon',
        coordinates: [coords]
      },
      properties: {}
    })
  }
}

const completeRectangle = (start: [number, number], end: [number, number]) => {
  if (!map) return
  
  // Create rectangle coordinates from start and end points
  const coords = createRectangleCoordinates(start, end)
  
  // Skip if rectangle is too small
  if (Math.abs(start[0] - end[0]) < 0.0001 && Math.abs(start[1] - end[1]) < 0.0001) {
    return
  }
  
  // Create a new rectangle feature
  const newRectangle: GeoJSON.Feature = {
    type: 'Feature',
    geometry: {
      type: 'Polygon',
      coordinates: [coords]
    },
    properties: {
      id: Date.now().toString()
    }
  }
  
  // Add to collection
  rectangleCollection.value.features.push(newRectangle)
  
  // Update the map source
  const source = map.getSource('rectangle-source') as mapboxgl.GeoJSONSource
  if (source) {
    source.setData(rectangleCollection.value)
  }
}

const createRectangleCoordinates = (start: [number, number], end: [number, number]): [number, number][] => {
  return [
    [start[0], start[1]],
    [end[0], start[1]],
    [end[0], end[1]],
    [start[0], end[1]],
    [start[0], start[1]] // Close the polygon
  ]
}

const toggleDrawMode = () => {
  isDrawMode.value = !isDrawMode.value
  
  if (!isDrawMode.value) {
    // Reset drawing state when exiting draw mode
    isDrawing.value = false
    startPoint.value = null
    updateTempRectangle([0, 0], [0, 0])
  }
  
  // Change cursor based on draw mode
  if (map) {
    map.getCanvas().style.cursor = isDrawMode.value ? 'crosshair' : ''
  }
}

const clearRectangles = () => {
  if (!map) return
  
  // Clear all rectangles
  rectangleCollection.value.features = []
  
  // Update the map source
  const source = map.getSource('rectangle-source') as mapboxgl.GeoJSONSource
  if (source) {
    source.setData(rectangleCollection.value)
  }
}

onMounted(() => {
  initMap()
})

onUnmounted(() => {
  map?.remove()
  map = null
})
</script>

<style scoped>
.map-container {
  width: 100%;
  height: 100vh;
  position: relative;
}

#map {
  width: 100%;
  height: 100%;
}

.control-panel {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 1;
  background: white;
  padding: 10px;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  display: flex;
  gap: 8px;
}

button {
  padding: 8px 16px;
  background: #2c3e50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #34495e;
}
</style> 


代码讲解

1. 初始化地图
  • 引入 Mapbox GL JS:通过 <script><link> 标签引入 Mapbox GL JS 的 JavaScript 和 CSS 文件。
  • 设置访问令牌:将 mapboxgl.accessToken 设置为你的 Mapbox 访问令牌(需自行申请)。
  • 创建地图实例:使用 mapboxgl.Map 配置地图,指定容器 ID(map)、地图样式(streets-v11)、中心点和缩放级别。
2. 监听鼠标事件
  • 鼠标点击 (clik)
    • 通过 e.lngLat 获取鼠标按下时的经纬度,存储在 startPoint 中。
    • 绑定 mousemove,开始绘制流程。
  • 鼠标移动 (mousemove)
    • 调用 drawRectangle 函数,实时更新矩形形状。
  • 鼠标再次点击 (clik)
    • 调用 stopDrawing 函数,移除事件监听器,结束绘制。
3. 绘制矩形
  • 计算坐标:根据 startPoint 和当前鼠标位置 endPoint,生成矩形的四个顶点坐标,形成闭合的多边形。
  • 创建 GeoJSON:将坐标封装为 GeoJSON 格式的 Polygon 类型。
  • 更新地图
    • 如果地图上已有 rectangle 数据源,则通过 setData 更新数据。
    • 如果没有,则通过 addSource 添加数据源,并通过 addLayer 创建一个填充图层来显示矩形。
  • 样式设置:矩形填充颜色为 #088(青色),透明度为 0.5
4. 结束绘制
  • stopDrawing 函数中,使用 map.off 移除 mousemovemouseup 的事件监听器,确保绘制过程不会重复触发。
  • 你可以选择在此处保存矩形数据(例如存储到数组中)或添加其他功能。
    在这里插入图片描述

注意事项

  • 访问令牌:确保将 'YOUR_MAPBOX_ACCESS_TOKEN' 替换为你的实际 Mapbox 访问令牌,否则地图无法加载。
  • 单一矩形:当前代码只支持绘制一个矩形,绘制完成后会覆盖之前的矩形。如需支持多个矩形,可以为每个矩形生成唯一的 ID 并创建独立图层。
  • 扩展功能:你可以添加按钮或逻辑来重置地图状态,或允许用户删除已绘制的矩形。

通过以上代码可以在 Mapbox GL JS 地图上实现鼠标绘制矩形的功能。这一功能适用于区域选择、地图标注等场景,具有很强的实用性。


网站公告

今日签到

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