基于 OpenLayers 实现态势地图手动标绘航线、作战区域及点位悬浮弹窗
需求基于 OpenLayers 技术框架实现地图交互效果支持操作人员在地图上手动绘制飞机运动轨迹、舰船航行轨迹以及作战区域范围当鼠标光标移动悬浮至飞机、舰船点位之上时页面弹出悬浮信息面板展示当前对应装备的详细数据信息。html部分div styleheight: 100vh;width: 100vw el-dropdown split-button sizemini :disabledisDelete stylemargin-right: 10px clickclickDraw {{isDraw ? 结束绘制 : 开始绘制}}{{drawTypeText}} template #dropdown el-dropdown-menu el-dropdown-item click.nativechangeDrawType(plane)飞机/el-dropdown-item el-dropdown-item click.nativechangeDrawType(ship)舰船/el-dropdown-item /el-dropdown-menu /template /el-dropdown el-button :disabled!isDraw sizemini clickclearDraw清除绘制/el-button el-button sizemini :disabledisDraw clickdeleteDraw{{isDelete ? 确认删除 : 删除轨迹}}/el-button !-- 地图容器 -- div idmap classmap/div !-- 地图需要展示的div内容 -- div styledisplay: none div idoverPlay div classoverPlayText v-ifhoverPointInfo.type plane名称飞燕一号/div div classoverPlayText v-ifhoverPointInfo.type ship名称泰坦尼克号/div div classoverPlayText v-ifhoverPointInfo.type plane飞行速度2.25马赫2400公里/小时/div div classoverPlayText v-ifhoverPointInfo.type ship航速40节/div div classoverPlayText v-ifhoverPointInfo.type event在这拐了个弯/div /div /div /div需要定义的数据如下data(){ return { map: null, areaLayer: null, //区域 pointLayer: null, //点 trailLayer: null, //轨迹 pointInfo: {}, //鼠标点击点信息 hoverPointInfo: {}, //鼠标移入点信息 drawLayer: null, //绘图的轨迹 pointTempLayer: null, //绘制轨迹时添加的临时点 isDraw: false, //是否正在绘制 isDelete: false, //是否在删除 clickPoints: [], //绘制时暂存的点 tempLine: null, //临时边 tempPoint: null, //临时点 drawType: , //地图绘制类型 } }, computed: { drawTypeText(){ let text let map { plane: 飞机, ship: 船舰, } if(this.drawType){ text map[this.drawType] } return (text ? (( text )) : ) } },初始化地图此方法完成地图实例创建、各类矢量图层初始化、地图交互事件绑定加载基础底图资源同时注册点位选中、鼠标悬浮监听逻辑。initMap(){ this.map null this.layer null this.pointLayer null this.trailLayer null // 图层 this.layer new TileLayer({ source: new XYZ({ visible: true, url: http://webrd01.is.autonavi.com/appmaptile?x{x}y{y}z{z}langzh_cnsize2scale1style8, wrapX: true, }), }) //区域 this.areaLayer new Vector({ source: new VectorSource(), }) //点 this.pointLayer new Vector({ source: new VectorSource(), }) //轨迹 this.trailLayer new Vector({ source: new VectorSource(), }) //临时点 this.pointTempLayer new Vector({ source: new VectorSource(), }) // 初始化地图到指定DOM元素 this.map new Map({ layers: [this.layer, this.trailLayer, this.areaLayer, this.pointLayer, this.pointTempLayer], target: map, view: new View({ projection: EPSG:4326, center: [135.403218, 30.92372], zoom: 4, maxZoom: 11, constrainResolution: true, // 设置缩放级别为整数 smoothResolutionConstraint: false, // 关闭无级缩放地图 }), }); //鼠标点击点图层获取该点的数据 let selectInteraction new Select({ layers: [this.pointLayer], }); this.map.addInteraction(selectInteraction) selectInteraction.on(select, e { const selectedFeature (e.selected || [])[0]; if(selectedFeature){ this.pointInfo selectedFeature.getProperties() console.log(this.pointInfo) } }) //鼠标移入显示id为overPlay的div let hoverFeature null this.map.on(pointermove,(e) { let feature this.map.forEachFeatureAtPixel(e.pixel, (feature, layer) { return feature },{hitTolerance: 5}) //有feature且feature变化才执行 if(feature ! hoverFeature){ hoverFeature feature if(feature feature.getGeometry().getType() Point){ this.map.getTargetElement().style.cursor pointer; this.hoverPointInfo feature.getProperties() this.addOverlay(this.hoverPointInfo.coords,overPlay) }else{ this.map.getTargetElement().style.cursor ; this.hoverPointInfo {} this.removeOverlays() } } }) },数据上图模拟接口返回数据模拟接口返回地理数据将作战区域、飞机点位、舰船点位、运动轨迹、轨迹拐点批量渲染至地图上。setTimeout(() { // //区域 let data1 [ [130.403218, 10.92372], [138.403218, 8.92372], [140.403218, 15.92372], [135.403218, 16.92372], ] let data2 [ [160.403218, 24.92372], [165.403218, 29.92372], [164.403218, 32.92372], [160.403218, 27.92372], ] this.addArea([data1,data2]) let data3 [ { id: 5, type: plane, coords: [177.403218, 27.92372], }, { id: 6, type: plane, coords: [120.403218, 12.92372], }, ] data3.forEach(e { this.addPoint(plane,e) }) let data4 [ { id: 7, type: ship, coords: [165.403218, 28.92372], }, { id: 8, type: ship, coords: [138.403218, 14.92372], }, ] data4.forEach(e { this.addPoint(ship,e) }) let data5 [ [ [177.403218, 27.92372], [150.403218, 10.92372], [120.403218, 12.92372], ], [ [165.403218, 28.92372], [138.403218, 14.92372], ] ] this.addTrail(red,data5) this.addPoint(red,{ id: 10, type: event, coords: [150.403218, 10.92372], }) // this.addHitArea(8000,1500) },2000)地图上加区域记录用户在绘制时点击地图点的位置在地图的相应图层绘制成多边形。//地图上加区域 addArea(data){ let feature new Feature({ geometry: new Polygon(data), }) feature.setStyle(mapStyle.redAreaStyle) this.areaLayer.getSource().addFeature(feature) },地图上加点根据点位类型区分飞机、舰船、轨迹拐点加载不同样式将点位渲染至点位图层。//地图上加点 addPoint(type, data){ if(type plane){ let point new Point(data.coords) let pointFeature new Feature({ geometry: point, ...data }) pointFeature.setStyle(mapStyle.pointRedPlane) this.pointLayer.getSource().addFeature(pointFeature) } if(type ship){ let point new Point(data.coords) let pointFeature new Feature({ geometry: point, ...data }) pointFeature.setStyle(mapStyle.pointRedShip) this.pointLayer.getSource().addFeature(pointFeature) } if(type red){ let point new Point(data.coords) let pointFeature new Feature({ geometry: point, ...data }) pointFeature.setStyle(mapStyle.pointRed) this.pointLayer.getSource().addFeature(pointFeature) } },地图上加轨迹记录用户在绘制时点击地图点的位置在地图的相应图层连点成线。//地图上加轨迹 addTrail(type,data){ data.forEach(e { let feature new Feature({ geometry: new LineString(e), }) feature.setStyle(mapStyle.redLineArrow(feature)) this.trailLayer.getSource().addFeature(feature) }) },地图上加div调用addOverlay方法后会创建dom避免出现过多无用dom情况下次新增时只改变overlay的位置。//地图上加div addOverlay(data,id){ if(!this.map.getOverlayById(id)){ let marker new Overlay({ id, position: data, element: document.getElementById(id), offset: [10, 20] }); this.map.addOverlay(marker); marker.setPositioning(top-left); } else{ let marker this.map.getOverlayById(id) marker.setPosition(data) } },地图上删除div使用插件的api删除overlay后再次调用addOverlay方法新增overlayoverlay显示异常。换个思路用户想要删除overlay时为overlay设置位置为 undefined 隐藏overlay。//地图上删除div removeOverlays() { let overlays this.map.getOverlays(); for (let i 0, len overlays.getLength(); i len; i) { overlays.item(i).setPosition(undefined) } },手动绘制轨迹点击开启绘制按钮后为地图绑定点击监听事件操作人员点击地图采集坐标生成临时点位当采集点位数量不少于两个时自动连线生成临时轨迹。点击结束绘制按钮之后移除地图点击事件将临时点位、临时线路转换为正式持久化的点位要素与轨迹线路。//修改绘制类型 changeDrawType(type){ this.drawType type }, //开始绘制地图 clickDraw(){ if(this.isDraw){ this.endDraw() }else{ if (!this.drawType) { this.$message.warning(请选择绘制类型) return } this.drawTrail() } }, //进入开始手动绘制状态 drawTrail(){ if(!this.isDraw){ this.isDraw true this.clickPoints [] this.map.on(click, this.handleMapClick) } }, //结束手动绘制状态 endDraw(){ if(this.isDraw){ this.isDraw false this.map.un(click, this.handleMapClick) //唯一标识,便于统一管理 let groupId point- Date.now() //临时点改为真实点 if(this.clickPoints){ this.pointTempLayer.getSource().clear() this.tempPoint null this.clickPoints.forEach((e, index) { if(index 0 || index (this.clickPoints.length - 1)){ this.addPoint(this.drawType, { type: this.drawType, coords: e, groupId }) } }) } //临时线改为真实线 if(this.clickPoints.length 2){ let feature new Feature({ geometry: new LineString(this.clickPoints), groupId }) feature.setStyle(mapStyle.redLineArrow(feature)) this.trailLayer.getSource().addFeature(feature) if(this.tempLine){ this.trailLayer.getSource().removeFeature(this.tempLine) this.tempLine null } this.clickPoints [] } } }, //绘制的点击事件 handleMapClick(e){ if(!this.isDraw) return; const coord e.coordinate this.clickPoints.push(coord) if(this.tempLine){ this.trailLayer.getSource().removeFeature(this.tempLine) } if(this.clickPoints.length 2){ this.tempLine new Feature({ geometry: new LineString(this.clickPoints), }) this.tempLine.setStyle(mapStyle.tempLineArrow(this.tempLine)) this.trailLayer.getSource().addFeature(this.tempLine) } if(coord){ this.tempPoint new Feature({ geometry: new Point(coord) }) this.tempPoint.setStyle(mapStyle.positionPointTemp) this.pointTempLayer.getSource().addFeature(this.tempPoint) } }清除绘制清空绘制过程中产生的所有临时点位与临时线路重置坐标集合用于绘制中途放弃时清理画布临时要素。//清除绘制 clearDraw(){ this.pointTempLayer.getSource().clear() this.tempPoint null if(this.tempLine){ this.trailLayer.getSource().removeFeature(this.tempLine) this.tempLine null } this.clickPoints [] },删除绘制内容手动绘制生成的点位、轨迹线路会绑定 groupId 属性作为唯一分组标识系统仅允许删除带有 groupId 属性的手绘要素预加载的静态轨迹无法执行删除操作。点击删除模式后点击地图对应轨迹即可删除同一分组下全部点位与线路。//删除轨迹 deleteDraw(){ this.isDelete !this.isDelete if(this.isDelete){ this.map.on(click,this.handleMapDelete) }else{ this.map.un(click,this.handleMapDelete) } }, handleMapDelete(e){ let feature this.map.forEachFeatureAtPixel(e.pixel, (feature, layer) { return feature },{hitTolerance: 5}) if(!feature.get(groupId)){ this.$message.warning(该轨迹非手绘轨迹,不可删除) return } const pointFeatures this.pointLayer.getSource().getFeatures() pointFeatures.forEach(f { if(f.get(groupId) feature.get(groupId)){ this.pointLayer.getSource().removeFeature(f) } }) const lineFeatures this.trailLayer.getSource().getFeatures() lineFeatures.forEach(f { if(f.get(groupId) feature.get(groupId)){ this.trailLayer.getSource().removeFeature(f) } }) },样式统一封装地图各类矢量要素渲染样式包含作战区域填充样式、飞机图标、舰船图标、轨迹线段、临时绘制要素样式集中管理方便后期统一调整可视化效果。import Fill from ol/style/fill; import Stroke from ol/style/stroke; import Style from ol/style/style; import Icon from ol/style/icon; import Circle from ol/style/circle; import Point from ol/geom/point; //红色多边形样式 function redAreaStyle(){ let fillColor new Fill({ color: rgba(255, 0, 0, 0.2) }) let stroke new Stroke({ color: red, width: 1, }) return new Style({ stroke, fillColor }) } //红色船 function pointRedPlane(){ return new Style({ image: new Icon({ src: require(../img/redPlane.svg), scale: 0.2, //缩放比例 }) }) } //红色飞机 function pointRedShip(){ return new Style({ image: new Icon({ src: require(../img/redShip.svg), scale: 0.2, //缩放比例 }) }) } //红色点 function pointRed(){ return new Style({ image: new Circle({ radius: 5, fill: new Fill({ color: red }) }) }) } //红色轨迹线 function redLineArrow(feature) { let geometry feature.getGeometry(); let styles [new Style({ stroke: new Stroke({ color: #d71106, width: 2, lineDash: [6,5] }) })]; geometry.forEachSegment(function (start, end) { let dx end[0] - start[0]; let dy end[1] - start[1]; let rotation Math.atan2(dy, dx); const kx (end[0] start[0]) / 2 const ky (end[1] start[1]) / 2 console.log(kx,ky) //arrows styles.push(new Style({ geometry: new Point([kx, ky]), image: new Icon({ src: require(../img/arrow_red.png), anchor: [0.75, 0.5], rotateWithView: false, rotation: -rotation }) })); }); return styles } //临时箭头 function tempLineArrow(feature) { let geometry feature.getGeometry(); let styles [new Style({ stroke: new Stroke({ color: #ffcc33, width: 2, lineDash: [6,5] }) })]; geometry.forEachSegment(function (start, end) { let dx end[0] - start[0]; let dy end[1] - start[1]; let rotation Math.atan2(dy, dx); const kx (end[0] start[0]) / 2 const ky (end[1] start[1]) / 2 //arrows styles.push(new Style({ geometry: new Point([kx, ky]), image: new Icon({ src: require(../img/arrow.png), anchor: [0.75, 0.5], rotateWithView: false, rotation: -rotation }) })); }); return styles; } //临时点 function positionPointTemp(){ return new Style({ image: new Circle({ radius:5, fill:new Fill({ color: #ffcc33 }) }), }); } let mapStyle { redAreaStyle, pointRedPlane, pointRedShip, pointRed, redLineArrow, positionPointTemp, tempLineArrow, } export default mapStyle项目地址

相关新闻

C++进阶(11):C++ IO 流

C++进阶(11):C++ IO 流

💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分…

2026/8/1 20:57:36 阅读更多 →
3分钟搞定iPhone USB网络共享驱动安装:Windows用户的终极救星

3分钟搞定iPhone USB网络共享驱动安装:Windows用户的终极救星

3分钟搞定iPhone USB网络共享驱动安装:Windows用户的终极救星 【免费下载链接】Apple-Mobile-Drivers-Installer Powershell script to easily install Apple USB and Mobile Device Ethernet (USB Tethering) drivers on Windows! 项目地址: https://gitcode.com…

2026/8/1 20:57:36 阅读更多 →
【Easylive】consumes = MediaType.MULTIPART_FORM_DATA_VALUE 与 @RequestPart

【Easylive】consumes = MediaType.MULTIPART_FORM_DATA_VALUE 与 @RequestPart

【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版 consumes MediaType.MULTIPART_FORM_DATA_VALUE 的作用 1. 定义请求的数据格式 • 作用:告诉 Feign 和 HTTP 客户端,这个接口 接收的是 multipart/form-data 格式的…

2026/8/1 20:57:36 阅读更多 →

最新新闻

B站缓存视频合并终极指南:Android上免费开源解决方案

B站缓存视频合并终极指南:Android上免费开源解决方案

B站缓存视频合并终极指南:Android上免费开源解决方案 【免费下载链接】BilibiliCacheVideoMerge 🔥🔥Android上将bilibili缓存视频合并导出为mp4,支持安卓5.0 ~ 13,视频挂载弹幕播放(Android consolidates and exports…

2026/8/1 21:42:53 阅读更多 →
WPS-Zotero实战指南:科研写作效率提升的终极解决方案

WPS-Zotero实战指南:科研写作效率提升的终极解决方案

WPS-Zotero实战指南:科研写作效率提升的终极解决方案 【免费下载链接】WPS-Zotero An add-on for WPS Writer to integrate with Zotero. 项目地址: https://gitcode.com/gh_mirrors/wp/WPS-Zotero 还在为论文写作中的文献引用而烦恼吗?WPS-Zoter…

2026/8/1 21:42:53 阅读更多 →
技术揭秘:pan-baidu-download如何用Python巧妙破解百度网盘下载限制

技术揭秘:pan-baidu-download如何用Python巧妙破解百度网盘下载限制

技术揭秘:pan-baidu-download如何用Python巧妙破解百度网盘下载限制 【免费下载链接】pan-baidu-download 百度网盘下载脚本 项目地址: https://gitcode.com/gh_mirrors/pa/pan-baidu-download 你是否曾为百度网盘的下载速度而烦恼?当大文件下载变…

2026/8/1 21:42:53 阅读更多 →
Glaze高级技巧:Timeline序列与并行动画的5个实战案例解析

Glaze高级技巧:Timeline序列与并行动画的5个实战案例解析

Glaze高级技巧:Timeline序列与并行动画的5个实战案例解析 【免费下载链接】glaze The utility-based animation framework for the web. 项目地址: https://gitcode.com/gh_mirrors/glaz/glaze Glaze作为基于实用工具的Web动画框架,通过声明式语法…

2026/8/1 21:42:53 阅读更多 →
如何用OpenCore Legacy Patcher免费升级老款Mac电脑:让你的旧设备重获新生

如何用OpenCore Legacy Patcher免费升级老款Mac电脑:让你的旧设备重获新生

如何用OpenCore Legacy Patcher免费升级老款Mac电脑:让你的旧设备重获新生 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为老款Mac电脑无法…

2026/8/1 21:42:53 阅读更多 →
如何用CNKI-download批量获取知网文献:学术研究者的智能助手

如何用CNKI-download批量获取知网文献:学术研究者的智能助手

如何用CNKI-download批量获取知网文献:学术研究者的智能助手 【免费下载链接】CNKI-download :frog: 知网(CNKI)文献下载及文献速览爬虫 (Web Scraper for Extracting Data) 项目地址: https://gitcode.com/gh_mirrors/cn/CNKI-download 你是否曾为收集大量…

2026/8/1 21:41:52 阅读更多 →

日新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/1 0:00:48 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/1 0:00:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/1 0:00:48 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/8/1 13:02:46 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/8/1 5:19:34 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/8/1 10:33:33 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/1 0:00:48 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/1 0:00:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/1 0:00:48 阅读更多 →