基于 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项目地址