openlayer的基本使用(区域绘制、点线绘制、手动绘制轨迹)
实现如图所示效果:
html部分
<div style="height: 100vh;width: 100vw"><el-dropdown split-button size="mini" :disabled="isDelete" style="margin-right: 10px" @click="clickDraw">{{isDraw ? '结束绘制' : '开始绘制'}}{{drawTypeText}}<template #dropdown><el-dropdown-menu><el-dropdown-item @click.native="changeDrawType('plane')">飞机</el-dropdown-item><el-dropdown-item @click.native="changeDrawType('ship')">舰船</el-dropdown-item></el-dropdown-menu></template></el-dropdown><el-button :disabled="!isDraw" size="mini" @click="clearDraw">清除绘制</el-button><el-button size="mini" :disabled="isDraw" @click="deleteDraw">{{isDelete ? '确认删除' : '删除轨迹'}}</el-button><!-- 地图容器 --><div id="map" class="map"></div><!-- 地图需要展示的div内容 --><div style="display: none"><div id="overPlay"><div class="overPlayText" v-if="hoverPointInfo.type == 'plane'">名称:飞燕一号</div><div class="overPlayText" v-if="hoverPointInfo.type == 'ship'">名称:泰坦尼克号</div><div class="overPlayText" v-if="hoverPointInfo.type == 'plane'">飞行速度:2.25马赫(2400公里/小时)</div><div class="overPlayText" v-if="hoverPointInfo.type == 'ship'">航速:40节</div><div class="overPlayText" v-if="hoverPointInfo.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 = nullthis.layer = nullthis.pointLayer = nullthis.trailLayer = null// 图层this.layer = new TileLayer({source: new XYZ({visible: true,url: 'http://webrd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=2&scale=1&style=8',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的divlet hoverFeature = nullthis.map.on('pointermove',(e) => {let feature = this.map.forEachFeatureAtPixel(e.pixel, (feature, layer) => {return feature},{hitTolerance: 5})//有feature且feature变化才执行if(feature != hoverFeature){hoverFeature = featureif(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()}}})},
地图上加区域
//地图上加区域
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
删除overlay后,再次调用addOverlay方法新增overlay不显示,所以鼠标移除时隐藏overlay。调用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
//地图上删除div
removeOverlays() {let overlays = this.map.getOverlays();for (let i = 0, len = overlays.getLength(); i < len; i++) {overlays.item(i).setPosition(undefined)}
},
手动绘制轨迹
点击开始绘制按钮后为地图新增点击事件,用户需要在地图上点击地图生成临时点,当临时点数量大于等于2时,临时点连接生成临时边,当点击结束绘制按钮后,关闭该点击事件,临时点和临时线转化为真实点和真实线。
//修改绘制类型
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 = truethis.clickPoints = []this.map.on('click', this.handleMapClick)}
},
//结束手动绘制状态
endDraw(){if(this.isDraw){this.isDraw = falsethis.map.un('click', this.handleMapClick)//唯一标识,便于统一管理let groupId = 'point-' + Date.now()//临时点改为真实点if(this.clickPoints){this.pointTempLayer.getSource().clear()this.tempPoint = nullthis.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.coordinatethis.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 = nullif(this.tempLine){this.trailLayer.getSource().removeFeature(this.tempLine)this.tempLine = null}this.clickPoints = []
},
删除绘制内容
临时点和临时线转为真实点和真实线时,为其加上groupId属性表示该轨迹为手绘轨迹,仅有手绘轨迹可删除。该删除为删除掉有groupId的真实点和真实线。
//删除轨迹
deleteDraw(){this.isDelete = !this.isDeleteif(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]) / 2const ky = (end[1] + start[1]) / 2console.log(kx,ky)//arrowsstyles.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]) / 2const ky = (end[1] + start[1]) / 2//arrowsstyles.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
项目地址