实现如图所示效果:
html部分
{{isDraw ? '结束绘制' : '开始绘制'}}{{drawTypeText}}
飞机
舰船
清除绘制
{{isDelete ? '确认删除' : '删除轨迹'}}
需求需要的数据如下:
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}&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的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()
}
}
})
},
//地图上加区域
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)
})
},
删除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
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 = 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
项目地址