在地图开发中,测距是很常见的地图操作,网上也有很多测距的教程,但其实都是硬放代码,看到一大段的方法还是蛮懵的,不知从何下手,因此在此做个记录
先理清思路,测距主要分为鼠标左键点击事件、鼠标移动事件、创建线、计算距离、鼠标左键双击事件、清除
准备工作
在创建点击事件之前,需要先关闭地图默认的左键双击事件,并创建变量记录点击位置、移动距离等,之所以需要记录点击位置和最终定位,是因为不需要最后一个点,最终定位 = 记录位置[记录位置.length - 1]
// 取消双击事件
viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
var positions = [] // 记录位置
var poly = null // 记录线
var distance = 0 // 记录距离
var cartesian = null // 获取鼠标与地图的交点
var labelPt // 最终位置
鼠标左键点击事件
首次点击时,为测距的起点,可创建一个起点作为标记,触发鼠标移动事件绘制线后,再次点击可绘制一条线的终点,并根据测量方法返回该线的长度
// 鼠标左键点击事件
handler.setInputAction(function (ele) {
// 创建射线并获取交点
let ray = viewer.camera.getPickRay(ele.position)
cartesian = viewer.scene.globe.pick(ray, viewer.scene)
if (!Cesium.defined(cartesian)) return
if (positions.length === 0) {
positions.push(cartesian.clone())
}
positions.push(cartesian)
// 记录鼠标单击时的位置,异步计算贴地距离
labelPt = positions[positions.length - 1]
if (positions.length > 2) {
getSpaceDistance(positions)
} else if (positions.length == 2) {
//在三维场景中添加圆点
viewer.entities.add({
name: '_range',
id: "range",
position: labelPt,
point: {
pixelSize: 5,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
},
label: {
text: '起 点',
font: 'normal 18px SimHei',
fillColor: Cesium.Color.ORANGE, // 文本颜色
backgroundColor: Cesium.Color.WHITE, // 背景色
style: Cesium.LabelStyle.FILL, // 文本样式,轮廓
outlineWidth: 2, // 轮廓宽度
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 圆点位置
horizontalOrigin: Cesium.HorizontalOrigin.LEFT, // 文本的位置
pixelOffset: new Cesium.Cartesian2(0, -10), // 文本偏移量,Cartesian2
}
});
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
鼠标移动事件
监听此事件,并在移动的过程中创建线
// 鼠标移动事件
handler.setInputAction(function (ele) {
let ray = viewer.camera.getPickRay(ele.endPosition)
cartesian = viewer.scene.globe.pick(ray, viewer.scene)
// 判断是否定义该对象
if (!Cesium.defined(cartesian)) {
return
}
if (positions.length >= 2) {
if (!Cesium.defined(poly)) {
// 移动时路径绘制
poly = new PolyLinePrimitive(positions)
} else {
positions.pop()
positions.push(cartesian)
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
// 创建线
var PolyLinePrimitive = (function () {
function line (positions) {
this.options = {
name: '_range',
polyline: {
show: true,
positions: [],
material: Cesium.Color.CORNFLOWERBLUE,
width: 3,
clampToGround: true
}
}
this.positions = positions
this.init()
}
line.prototype.init = function () {
var update = () => {
return this.positions
}
// 实时更改线的位置,更新线
this.options.polyline.positions = new Cesium.CallbackProperty(update, false)
viewer.entities.add(this.options)
}
return line
})()
鼠标双击事件
该事件作为结束测距事件,点击该事件时,绘制终点,并关闭掉双击事件。
//鼠标左键双击事件
handler.setInputAction(function (ele) {
// 创建射线并获取交点
let ray = viewer.camera.getPickRay(ele.position);
cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (!Cesium.defined(cartesian))
return;
if (positions.length === 0) {
positions.push(cartesian.clone());
}
positions.push(cartesian);
// 记录鼠标单击时的节点位置,异步计算贴地距离
labelPt = positions[positions.length - 1];
if (positions.length > 2) {
getSpaceDistance(positions);
} else if (positions.length === 2) {
// 在三维场景中添加Label
viewer.entities.add({
name: '_range',
position: labelPt,
point: {
pixelSize: 5,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.MINTCREAM,
outlineWidth: 2,
}
});
}
handler.destroy(); // 删除事件
handler = undefined;
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
计算距离
该方法是整个测距的灵魂,也是测距和测绘的区别所在,从第二次鼠标左键单击开始调用此方法,为每一条线计算出距离,并绘制在地图中
// 两点距离计算函数
function getSpaceDistance (positions) {
// 只计算最后一截,与前面累加, 因鼠标移动和左键单击事件,最后两个坐标点重复
let i = positions.length - 3
// 根据经纬度获取弧度
let pointCartographic1 = Cesium.Cartographic.fromCartesian(positions[i])
let pointCartographic2 = Cesium.Cartographic.fromCartesian(positions[i + 1])
getTerrainDistance(pointCartographic1, pointCartographic2)
}
// 贴地距离计算函数
function getTerrainDistance (poin1, poin2) {
var geodesic = new Cesium.EllipsoidGeodesic()
geodesic.setEndPoints(poin1, poin2)
var s = geodesic.surfaceDistance
var cartoPts = [poin1]
for (let i = 1000; i < s; i += 1000) {
var cartoPt = geodesic.interpolateUsingSurfaceDistance(i)
cartoPts.push(cartoPt)
}
cartoPts.push(poin2)
// 返回两点之间的距离
var promise = Cesium.sampleTerrain(viewer.terrainProvider, 2, cartoPts)
Cesium.when(promise, function (updatedPositions) {
for (let i = 0; i < updatedPositions.length - 1; i++) {
var geoD = new Cesium.EllipsoidGeodesic()
geoD.setEndPoints(updatedPositions[i], updatedPositions[i + 1])
var innerS = geoD.surfaceDistance
innerS = Math.sqrt(Math.pow(innerS, 2) + Math.pow(updatedPositions[i + 1].height - updatedPositions[i].height, 2))
distance += innerS
}
// 在三维场景中添加 label
var textDisance = distance > 10000 ? (distance / 1000.0).toFixed(2) + '公里' : distance.toFixed(2) + '里'
viewer.entities.add({
name: '_range',
position: labelPt,
point: {
poixeSize: 4,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.MINTCREAM,
outlineWidth: 2
},
label: {
text: textDisance,
font: '18px sans-serif',
fillColor: Cesium.Color.GOLD,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outLineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -10)
}
})
})
}
清除
清除就是是关闭事件且清除图形。当没关闭事件时,就会出现下面场景,第一次测距是正常的,但再次点击按钮时,又会触发点击事件,并以点击按钮的位置作为起点
- 清除几何
在创建线时,我们给赋予一个
name,这个值是几何的唯一标识符,我们以此为条件,查找到所有name为_range的几何并清除
// 清除线性几何
function removeGeometrys (viewer) {
let RangeArea = viewer.entities._entities._array
for (let i = 0; i < RangeArea.length; i++) {
if (RangeArea[i]._name === "_range") {
viewer.entities.remove(RangeArea[i]);
i--;
}
}
}
- 销毁事件 方法执行完后一定记得销毁监听,不然监听会全局存在,消耗性能
this.handler && this.handler.destroy()
this.handler = null