当前位置: 首页 > article >正文

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

以下是如何使用 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) returnmapboxgl.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 rectanglesmap?.addSource('rectangle-source', {type: 'geojson',data: rectangleCollection.value})// Add layer for completed rectanglesmap?.addLayer({id: rectangleLayerId,type: 'fill',source: 'rectangle-source',paint: {'fill-color': '#4e9af5','fill-opacity': 0.5,'fill-outline-color': '#0066cc'}})// Add layer for rectangle outlinemap?.addLayer({id: 'rectangle-outline',type: 'line',source: 'rectangle-source',paint: {'line-color': '#0066cc','line-width': 2}})// Add source for the temp rectangle being drawnmap?.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 rectanglemap?.addLayer({id: tempRectangleLayerId,type: 'fill',source: tempRectangleSourceId,paint: {'fill-color': '#4e9af5','fill-opacity': 0.3,'fill-outline-color': '#0066cc'}})// Add temp rectangle outline layermap?.addLayer({id: 'temp-rectangle-outline',type: 'line',source: tempRectangleSourceId,paint: {'line-color': '#0066cc','line-width': 2,'line-dasharray': [2, 2]}})// Event handlers for drawingsetupDrawingEventHandlers()})setMapInstance(map)
}const setupDrawingEventHandlers = () => {if (!map) return// First click - start drawingmap.on('click', (e) => {if (!isDrawMode.value) return// If not drawing yet, start a new rectangleif (!isDrawing.value) {isDrawing.value = truestartPoint.value = [e.lngLat.lng, e.lngLat.lat]// Initialize the temp rectangleupdateTempRectangle(startPoint.value, startPoint.value)} // If already drawing, complete the rectangleelse if (startPoint.value) {const endPoint: [number, number] = [e.lngLat.lng, e.lngLat.lat]// Complete the rectanglecompleteRectangle(startPoint.value, endPoint)// Reset drawing stateisDrawing.value = falsestartPoint.value = null// Clear temp rectangleupdateTempRectangle([0, 0], [0, 0])}})// Mouse move - update rectangle while in drawing mode, but after first clickmap.on('mousemove', (e) => {// Only update if we're in drawing mode AND we've made the first clickif (!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 pointsconst coords = createRectangleCoordinates(start, end)// Update the temporary rectangleconst geojsonSource = map.getSource(tempRectangleSourceId) as mapboxgl.GeoJSONSourceif (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 pointsconst coords = createRectangleCoordinates(start, end)// Skip if rectangle is too smallif (Math.abs(start[0] - end[0]) < 0.0001 && Math.abs(start[1] - end[1]) < 0.0001) {return}// Create a new rectangle featureconst newRectangle: GeoJSON.Feature = {type: 'Feature',geometry: {type: 'Polygon',coordinates: [coords]},properties: {id: Date.now().toString()}}// Add to collectionrectangleCollection.value.features.push(newRectangle)// Update the map sourceconst source = map.getSource('rectangle-source') as mapboxgl.GeoJSONSourceif (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.valueif (!isDrawMode.value) {// Reset drawing state when exiting draw modeisDrawing.value = falsestartPoint.value = nullupdateTempRectangle([0, 0], [0, 0])}// Change cursor based on draw modeif (map) {map.getCanvas().style.cursor = isDrawMode.value ? 'crosshair' : ''}
}const clearRectangles = () => {if (!map) return// Clear all rectanglesrectangleCollection.value.features = []// Update the map sourceconst source = map.getSource('rectangle-source') as mapboxgl.GeoJSONSourceif (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 地图上实现鼠标绘制矩形的功能。这一功能适用于区域选择、地图标注等场景,具有很强的实用性。

相关文章:

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

以下是如何使用 Mapbox GL JS 实现鼠标绘制矩形功能的详细代码和讲解。Mapbox GL JS 是一个强大的 JavaScript 库&#xff0c;可以用来创建交互式地图。下面将通过监听鼠标事件并动态更新地图图层来实现这一功能。 实现步骤 初始化地图 在 HTML 文件中引入 Mapbox GL JS 库&…...

《筋斗云的K8s容器化迁移》

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 88万阅读 1.6万收藏 文章目录 **第一章&#xff1a;斗战胜佛的延迟焦虑****第二章&#xff1a;微服务化的紧箍咒****第三章&#xff1a;混沌中的流量劫持****第四章&#xff1a;量子筋斗的终极形态****终章&…...

面试遇到的几个问题小记20250401

一、echarts设置数据的几种方式 在 ECharts 里&#xff0c;设置数据存在多种方式&#xff0c;下面为你详细介绍&#xff1a; 在初始化配置项时设置数据 这是最为常见的方式&#xff0c;也就是在创建 ECharts 实例的时候&#xff0c;于配置项 option 里直接设置数据。 // 基于…...

java swing 密码框如何在获取到焦点时,输入法自动切换为英文状态

一、java swing 密码框如何在获取到焦点时&#xff0c;输入法自动切换为英文状态 在 Java Swing 中&#xff0c;JPasswordField 用于输入密码&#xff0c;默认情况下&#xff0c;输入法状态不受控。要在获取焦点时自动切换为英文状态&#xff0c;可以通过以下步骤实现&#xf…...

图像处理中的Transformer Block实现与解析

图像处理中的Transformer Block实现与解析 随着深度学习技术的不断进步&#xff0c;Transformer结构在自然语言处理领域取得了显著的成功。近年来&#xff0c;这种注意力机制也被引入到计算机视觉任务中&#xff0c;展示了其强大的表现力和效果提升能力。本文将从代码实现的角…...

基于SpringBoot的“考研学习分享平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“考研学习分享平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体功能结构图 局部E-R图 系统首页界面 …...

Web3.0隐私计算与云手机的结合

Web3.0隐私计算与云手机的结合 Web3.0隐私计算与云手机的结合&#xff0c;标志着从“数据垄断”向“数据自主”的范式转变。通过技术互补&#xff0c;两者能够构建更安全、高效且用户主导的数字生态。尽管面临技术整合和成本挑战&#xff0c;但随着区块链、AI和分布式存储的成…...

视觉与激光点云 融合的 三维重建算法

以下是一些结合激光点云&#xff08;LiDAR&#xff09;与其他数据&#xff08;如图像、RGB-D等&#xff09;的三维重建算法&#xff0c;这类方法通过融合多模态数据提升重建的精度和完整性&#xff1a; ‌1. 传统几何融合方法‌ ‌(1) LiDAR 相机&#xff08;RGB/LiDAR Fusion…...

Linux上位机开发实践(超越MPP去开发产品)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于芯片厂商来说&#xff0c;肯定希望客户的应用和自己的芯片绑定地越紧密越好。最好就是&#xff0c;他们自己成为客户的独家供应商。但是对于嵌…...

雪花算法生成的主键存在哪些问题,为什么不能使用自增ID或者UUID做MySQL的主键

MySQL 分布式架构中的主键选择&#xff1a;自增ID、UUID与雪花算法 为什么MySQL分布式架构中不能使用自增主键&#xff1f; 在分布式架构中&#xff0c;自增主键存在以下问题&#xff1a; 主键冲突风险&#xff1a;多个数据库实例同时生成自增主键会导致ID重复分片不均匀&am…...

SpringBean模块(二)bean初始化(2)和容器初始化顺序的比较--引入ApplicationContextInitializer

前面介绍了获取容器可以让spring bean实现ApplicationContextAware&#xff0c;实际也是初始化执行了setApplicationContext接口&#xff0c; 初始化接口还可以借助一些注解或者spring bean的初始化方法&#xff0c;那么他们的执行顺序是什么样的呢&#xff1f; 一、验证&…...

【分享】内外网文件摆渡系统:让数据传输更安全更可靠

【分享】Ftrans内外网文件摆渡系统&#xff1a;让数据传输更安全更可靠&#xff01; 随着大数据时代的到来&#xff0c;数据的重要性日渐得到重视&#xff0c;数据作为数字经济时代下的基础性资源和战略性资源&#xff0c;是决定国家经济发展水平和竞争力的核心驱动力。以行业…...

2025年江苏省职业院校技能大赛 (高职组)大数据应用开发赛项任务书 (样题)

2025年江苏省职业院校技能大赛 &#xff08;高职组&#xff09;大数据应用开发赛项任务书 &#xff08;样题&#xff09; 背景描述&#xff1a;任务A&#xff1a;离线数据处理&#xff08;35分&#xff09;子任务一&#xff1a;数据抽取子任务三&#xff1a;指标计算 任务B&…...

前端学习记录之HTML

1. 网页 1.1 什么是网页 网站是指在因特网上根据一定的规则&#xff0c;使用HTML等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是HTML格式的文件&#xff0c;它要通过浏览器来阅读 网页是构成网站的基本元素。它通常由图片&#xff0c;…...

解释一下Unity碰撞的触发条件

Unity中碰撞的触发条件主要与物体的碰撞体&#xff08;Collider&#xff09;和刚体&#xff08;Rigidbody&#xff09;组件有关。具体来说&#xff0c;Unity的物理引擎会根据物体的配置来检测碰撞&#xff08;Collision&#xff09;和触发器&#xff08;Trigger&#xff09;事件…...

手机显示5GA图标的条件

最近有星友问在什么情况下才能显示5G-A&#xff1f;虽然这个我也不知道&#xff0c;但是我有几个运营商的5G终端白皮书&#xff0c;从上面就可以找到答案。 如上是几个运营商显示5G-A的条件&#xff0c;基本上考虑的都是3CC的情况&#xff0c;联通还有考虑200M CA 2CC的场景&am…...

Spring Boot 实现文件秒传功能

前言 在开发Web应用时&#xff0c;文件上传是一个常见需求。然而&#xff0c;当用户需要上传大文件或相同文件多次时&#xff0c;会造成带宽浪费和服务器存储冗余。此时可以使用文件秒传技术通过识别重复文件&#xff0c;实现瞬间完成上传的效果&#xff0c;大大提升了用户体验…...

使用AOP技术实现Java通用接口验签工具

一、背景 在给第三方提供接口时,我们需要对接口进行验签。具体来说,当外部系统调用我们的接口时,请求中需要携带一个签名,我们接收到请求后,会解析数据并校验签名是否正确,以确保请求的合法性和安全性。 为了在不同项目中方便地使用这一功能,我们将签名校验规则封装成一…...

Foldseek快速蛋白质结构比对

1. 下载和安装 Foldseek 如果只是单个蛋白质结构的序列比对&#xff0c;我们只需要用Foldseek 的网站服务 https://search.foldseek.com/search 上传我们的蛋白质结构并选择想要进行比对的数据库即可&#xff0c;这里不做重点讲解。做生物信息学研究&#xff0c;我们难免需要批…...

JAVA设计模式之适配器模式《太白金星有点烦》

太白金星握着月光凝成的鼠标&#xff0c;第108次检查南天门服务器的运行日志。这个刚从天枢院调来的三等仙官&#xff0c;此刻正盯着瑶池主机房里的青铜鼎发愁——鼎身上"天地同寿"的云纹间&#xff0c;漂浮着三界香火系统每分钟吞吐的十万条功德数据。看着居高不下的…...

w2ui 水平滚动移动 虚拟列 数据丢失

https://w2ui.com/web/docs/1.5/w2grid.disableCVS https://github.com/vitmalina/w2ui/issues/1398 解决方案来源 问题现象: 窗口缩小 导致多列 出现水平滚动,滚动时触发本地样式重绘,导致record undefined,从而引发多列报错 解决方案: 使用 disableCVS : true 一次加载到d…...

aarch64-none-elf-gcc与aarch64-linux-gnu-gcc

1. 场景描述 在Ubuntu 24.04.1 LTS x86_64架构下交叉编译能跑在aarch64架构下裸机程序&#xff0c;遇到缺aarch64-none-elf-gcc的情况&#xff0c;做此记录。 2. aarch64-none-elf-gcc与aarch64-linux-gnu-gcc 运行环境 aarch64-none-elf-gcc 生成的代码是 裸机程序&#xf…...

MySQL篇(一):慢查询定位及索引、B树相关知识详解

MySQL篇&#xff08;一&#xff09;&#xff1a;慢查询定位及索引、B树相关知识详解 MySQL篇&#xff08;一&#xff09;&#xff1a;慢查询定位及索引、B树相关知识详解一、MySQL中慢查询的定位&#xff08;一&#xff09;慢查询日志的开启&#xff08;二&#xff09;慢查询日…...

【清华大学】DeepSeek政务应用场景与解决方案

目录 一、政务数字化转型三阶段演进二、人工智能政务应用场景四大方向 三、技术方案核心技术 四、解决方案案例1. 公文写作2. 合同协议智能审查3. 行政执法4. 就业指导 五、风险及对策六、落地大四步法七、未来发展展望AI职业替代逻辑空间智能与具身智能人机共生 一、政务数字化…...

4.2 单相机引导机器人放料-仅考虑角度变化

【案例说明】 本案例产品在托盘中,角度变化不大(<15度);抓取没有问题,只是放的穴位只能容许3度的角度偏差,因此需要测量产品的角度。 思路是:机器人抓料后、去固定拍照位拍照(找到与标准照片的角度偏差),机器人在放料的位置上多旋转这个角度偏差,把产品放进去。 …...

洛谷题单1-P5704 【深基2.例6】字母转换-python-流程图重构

题目描述 输入一个小写字母&#xff0c;输出其对应的大写字母。例如输入 q[回车] 时&#xff0c;会输出 Q。 输入格式 无 输出格式 无 输入输出样例 输入 q输出 Q方式-upper() 代码 class Solution:staticmethoddef oi_input():"""从标准输入读取数据…...

论文阅读笔记:Denoising Diffusion Implicit Models (3)

0、快速访问 论文阅读笔记&#xff1a;Denoising Diffusion Implicit Models &#xff08;1&#xff09; 论文阅读笔记&#xff1a;Denoising Diffusion Implicit Models &#xff08;2&#xff09; 论文阅读笔记&#xff1a;Denoising Diffusion Implicit Models &#xff08…...

Git(八)如何在同一台电脑登录两个Git

目录 一、理解 SSH 密钥机制二、具体实现步骤1.删除GIT全局配置2.生成多个 SSH 密钥3.添加公钥到 Git 账户4.配置 SSH config 文件5.测试SSH key是否生效6.下载代码 三、Git仓库级别配置四、HTTPS方式的多账号管理 引言&#xff1a; 在日常开发中&#xff0c;我们经常会遇到需要…...

FPGA学习-基于 DE2-115 板的 Verilog 分秒计数器设计与按键功能实现

一、核心功能设计 按键暂停/继续&#xff1a;通过KEY1控制计时状态 按键消抖处理&#xff1a;20ms消抖周期消除机械抖动 硬件资源分配&#xff1a;符合DE2-115开发板引脚规范 二、核心模块实现详解 1. 顶层模块&#xff08;counter&#xff09; module counter(input CL…...

如何改电脑网络ip地址:一步步指导

有时我们需要更改电脑的网络IP地址以满足特定的网络需求。本文将为您提供一份详细的步骤指南&#xff0c;帮助您轻松完成电脑网络IP地址的更改。以下是更改计算机IP地址的分步指南&#xff0c;适用于常见的操作系统&#xff1a; 一、更换内网ip Windows 系统&#xff08;Win10…...