测量组件需要拥有距离测量、面积测量、角度测量。距离测量需要标注出起点以及起点到后面每一个点的阶段距离,最后一个点标注测量的总距离;面积测量是在地图上绘制一个多边形,完成绘制后标注出多边形的面积;角度测量是绘制三个点,将第二个点为角的顶点。
在绘制图像时有操作提示,有动态测量的结果。
1.ol/interaction/Draw
,用于绘制图形
2.ol/source/Vector
、ol/layer/Vector
,矢量数据源和图层,显示测量绘制的图形
3.ol/Overlay
,显示测量标记
4.ol/sphere/getLength
、ol/sphere/getArea
,计算长度和面积
5.ol/style/*
,设置样式
测量组件:
<template>
<div class="measure-tool">
<div class="ol-control"><button @click="measure('distence')">距</button></div>
<div class="ol-control"><button @click="measure('area')">面</button></div>
<div class="ol-control"><button @click="measure('angle')">角</button></div>
</div>
</template>
<script>
import { Draw } from "ol/interaction";
import { Vector as VectorSource } from "ol/source";
import { Vector as VectorLayer } from "ol/layer";
import Overlay from 'ol/Overlay';
import { Polygon, LineString } from 'ol/geom';
import Feature from 'ol/Feature';
import { unByKey } from 'ol/Observable'
import { getLength, getArea } from 'ol/sphere';
import Style from "ol/style/Style";
import Stroke from "ol/style/Stroke";
import Fill from "ol/style/Fill";
import Circle from "ol/style/Circle";
export default {
props: ["map"],//地图组件传值
data() {
return {
measureType: "diatence",
draw: null,
vectorLayer: null,
tipDiv: null,
pointermoveEvent: null, // 地图pointermove事件
sketchFeature: null, // 绘制的要素
geometryListener: null, // 要素几何change事件
measureResult: "0" // 测量结果
}
},
methods: {
creatDraw(type) {
let maxPoints = null;
if (this.measureType == "angle") maxPoints = 3
else maxPoints = null
// 矢量图层源
let vectorSource = new VectorSource({
wrapX: false
});
// 矢量图层
this.vectorLayer = new VectorLayer({
source: vectorSource,
style: new Style({
fill: new Fill({
color: 'rgba(252, 86, 49, 0.1)'
}),
stroke: new Stroke({
color: '#fc5531',
width: 3
}),
image: new Circle({
radius: 0,
fill: new Fill({
color: '#fc5531'
})
})
}),
name: "测量图层"
});
this.map.addLayer(this.vectorLayer)
this.draw = new Draw({
source: vectorSource,
type: type,
maxPoints: maxPoints,
style: new Style({
fill: new Fill({
color: 'rgba(252, 86, 49, 0.1)'
}),
stroke: new Stroke({
color: '#fc5531',
lineDash: [10, 10],
width: 3
}),
image: new Circle({
radius: 0,
fill: new Fill({
color: '#fc5531'
})
})
}),
// 绘制时点击处理事件
condition: (evt) => {
// 测距时添加点标注
if (this.measureResult != "0" && !this.map.getOverlayById(this.measureResult) && this.measureType == "distence")
this.creatMark(null, this.measureResult, this.measureResult).setPosition(evt.coordinate)
return true
}
});
this.map.addInteraction(this.draw);
/**
* 绘制开始事件
*/
this.draw.on("drawstart", e => {
this.sketchFeature = e.feature
let proj = this.map.getView().getProjection()
//******距离测量开始时*****//
if (this.measureType == "distence") {
this.creatMark(null, "起点", "start").setPosition(this.map.getCoordinateFromPixel(e.target.downPx_))
this.tipDiv.innerHTML = "总长:0 m单击确定地点,双击结束";
this.geometryListener = this.sketchFeature.getGeometry().on('change', (evt) => {
this.measureResult = this.distenceFormat(getLength(evt.target, { "projection": proj, "radius": 6378137 }))
this.tipDiv.innerHTML = "总长:" + this.measureResult + "单击确定地点,双击结束";
})
}
//******面积测量开始时*****//
else if (this.measureType == "area") {
this.tipDiv.innerHTML = "面积:0 m2继续单击确定地点";
this.geometryListener = this.sketchFeature.getGeometry().on('change', (evt) => {
if (evt.target.getCoordinates()[0].length < 4) this.tipDiv.innerHTML = "面积:0m2继续单击确定地点";
else {
this.measureResult = this.formatArea(getArea(evt.target, { "projection": proj, "radius": 6378137 }))
this.tipDiv.innerHTML = "面积:" + this.measureResult + "单击确定地点,双击结束";
}
})
}
//******角度测量开始时*****//
else if (this.measureType == "angle") {
this.tipDiv.innerHTML = "继续单击确定顶点";
this.geometryListener = this.sketchFeature.getGeometry().on('change', (evt) => {
if (evt.target.getCoordinates().length < 3) this.tipDiv.innerHTML = "继续单击确定顶点";
else {
this.measureResult = this.formatAngle(evt.target)
this.tipDiv.innerHTML = "角度:" + this.measureResult + "继续单击结束";
}
})
}
});
/**
* 绘制开始事件
*/
this.draw.on("drawend", e => {
let closeBtn = document.createElement('span');
closeBtn.innerHTML = "×";
closeBtn.title = "清除测量"
closeBtn.style = "width: 10px;height:10px;line-height: 12px;text-align: center;border-radius: 5px;display: inline-block;padding: 2px;color: rgb(255, 68, 0);border: 2px solid rgb(255, 68, 0);background-color: rgb(255, 255, 255);font-weight: 600;position: absolute;top: -25px;right: -2px;cursor: pointer;";
closeBtn.addEventListener('click', () => {
this.clearMeasure()
})
//******距离测量结束时*****//
if (this.measureType == "distence") {
this.creatMark(closeBtn, null, "close1").setPosition(e.feature.getGeometry().getLastCoordinate());
this.creatMark(null, "总长:" + this.measureResult + "", "length").setPosition(e.feature.getGeometry().getLastCoordinate())
this.map.removeOverlay(this.map.getOverlayById(this.measureResult))
}
//******面积测量结束时*****//
else if (this.measureType == "area") {
this.creatMark(closeBtn, null, "close2").setPosition(e.feature.getGeometry().getInteriorPoint().getCoordinates());
this.creatMark(null, "总面积:" + this.measureResult + "", "area").setPosition(e.feature.getGeometry().getInteriorPoint().getCoordinates())
}
//******角度测量结束时*****//
else if (this.measureType == "angle") {
this.creatMark(closeBtn, null, "close3").setPosition(e.feature.getGeometry().getCoordinates()[1]);
this.creatMark(null, "角度:" + this.measureResult + "", "angle").setPosition(e.feature.getGeometry().getCoordinates()[1])
}
// 停止测量
this.stopMeasure();
});
},
/**
* 测量
*/
measure(type) {
if (this.draw != null) return false; // 防止在绘制过程再创建测量
this.measureType = type;
if (this.vectorLayer != null) this.clearMeasure();
this.tipDiv = document.createElement('div');
this.tipDiv.innerHTML = '单击确定起点';
this.tipDiv.className = "tipDiv";
this.tipDiv.style = "width:auto;height:auto;padding:4px;border:1px solid #fc5531;font-size:12px;background-color:#fff;position:relative;top:60%;left:60%;font-weight:600;"
let overlay = new Overlay({
element: this.tipDiv,
autoPan: false,
positioning: "bottom-center",
id: "tipLay",
stopEvent: false //停止事件传播到地图
});
this.map.addOverlay(overlay);
this.pointermoveEvent = this.map.on("pointermove", evt => {
overlay.setPosition(evt.coordinate)
})
if (this.measureType == "distence" || this.measureType == "angle") {
this.creatDraw("LineString")
}
else if (this.measureType == "area") {
this.creatDraw("Polygon")
}
},
/**
* 创建标记
*/
creatMark(markDom, txt, idstr) {
if (markDom == null) {
markDom = document.createElement('div');
markDom.innerHTML = txt
markDom.style = "width:auto;height:auto;padding:4px;border:1px solid #fc5531;font-size:12px;background-color:#fff;position:relative;top:60%;left:60%;font-weight:600;"
}
let overlay = new Overlay({
element: markDom,
autoPan: false,
positioning: "bottom-center",
id: idstr,
stopEvent: false
});
this.map.addOverlay(overlay)
return overlay;
},
/**
* 格式化距离结果输出
*/
distenceFormat(length) {
let output;
if (length > 100) {
output = (Math.round(length / 1000 * 100) / 100) + ' ' + 'km'; //换算成km单位
} else {
output = (Math.round(length * 100) / 100) + ' ' + 'm'; //m为单位
}
return output;//返回线的长度
},
/**
* 格式化面积输出
*/
formatArea(area) {
let output;
if (area > 10000) {
output = (Math.round(area / 1000000 * 100) / 100) + ' ' + 'km2'; //换算成km单位
} else {
output = (Math.round(area * 100) / 100) + ' ' + 'm2';//m为单位
}
return output; //返回多边形的面积
},
/**
* 计算角度输出
*/
formatAngle(line) {
var coordinates = line.getCoordinates();
var angle = '0°';
if (coordinates.length == 3) {
const disa = getLength(new Feature({
geometry: new LineString([coordinates[0], coordinates[1]])
}).getGeometry(), {
radius: 6378137,
projection: this.map.getView().getProjection()
});
const disb = getLength(new Feature({
geometry: new LineString([coordinates[1], coordinates[2]])
}).getGeometry(), {
radius: 6378137,
projection: this.map.getView().getProjection()
});
const disc = getLength(new Feature({
geometry: new LineString([coordinates[0], coordinates[2]])
}).getGeometry(), {
radius: 6378137,
projection: this.map.getView().getProjection()
});
var cos = (disa * disa + disb * disb - disc * disc) / (2 * disa * disb); // 计算cos值
angle = (Math.acos(cos) * 180) / Math.PI; // 角度值
angle = angle.toFixed(2); // 结果保留两位小数
}
if (isNaN(angle)) return "0°"
else return angle + "°"; // 返回角度
},
/**
* 停止测量
*/
stopMeasure() {
this.tipDiv = null
this.map.removeInteraction(this.draw); // 移除绘制组件
this.draw = null;
this.map.removeOverlay(this.map.getOverlayById("tipLay")) // 移除动态提示框
},
/**
* 清除测量
*/
clearMeasure() {
this.vectorLayer.getSource().clear()
this.map.getOverlays().clear()
//移除监听事件
unByKey(this.pointermoveEvent) // 清除鼠标在地图的pointermove事件
unByKey(this.geometryListener) // 清除绘制图像change事件
this.pointermoveEvent = null;
this.geometryListener = null;
this.measureResult = "0"
}
}
}
</script>
<style scoped>
.measure-tool {
z-index: 1;
position: absolute;
left: 0.5em;
top: 6em;
width: 50px;
height: 40px;
}
.measure-tool div:nth-child(2) {
margin-top: 2em;
}
.measure-tool div:nth-child(3) {
margin-top: 4em;
}
</style>
地图组件引入测量组件:
<template>
<div id="map">
<measureTool v-if="map" :map="map"></measureTool>
</div>
</template>
这就是基于Vue实现的一个测量组件,有需要的朋友可以用用,有些不足的地方可以自己再优化一下,也欢迎指正,样式可以根据自己的需求修改,奥利给~