一、公共数据
const rangingObj = ref([]); // 测距点位集合
const totalRang = ref(0); // 测距距离长度
const isFinishRang = ref(false); // 是否为新的测量,true为是,false为否
// ----------角度测量----------
const angleObj = ref([]); // 角度测量线条点位集合
const anglePointObj = ref([]); // 角度测量点位集合
const isFinishAngle = ref(false); //是否为新的测量,true为是,false为否
// ----------面积测量----------
const areaObj = ref([]); // 面积测量点位集合
const isFinishArea = ref(false); // 是否为新的测量,true为是,false为否
let rightClickEvent = ref(null); // 右键单击事件
let mouseMoveType = ref(null); // 当前鼠标移动的操作类型
二、公共函数
1、屏幕坐标转经纬度
const converted = (x, y, height = 0) => {// 屏幕坐标转换为二维笛卡尔坐标let twoCoordinate = new Cesium.Cartesian2(x, y);// 获取屏幕坐标的对应椭球面位置let wordCoordinate = viewer.scene.camera.pickEllipsoid(twoCoordinate,viewer.scene.globe.ellipsoid);// 笛卡尔坐标转弧度let cartographic = Cesium.Cartographic.fromCartesian(wordCoordinate,viewer.scene.globe.ellipsoid,new Cesium.Cartographic());// Cesium.Math.toDegrees 将弧度转换成经纬度let lon = Cesium.Math.toDegrees(cartographic.longitude);let lat = Cesium.Math.toDegrees(cartographic.latitude);// 返回转换结果return {lon,lat,height,};
};
2、世界坐标转经纬度
const wordConverted = (x, y, z) => {// 坐标转换let cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic({x,y,z,});// 获取经纬度let lon = Cesium.Math.toDegrees(cartographic.longitude);let lat = Cesium.Math.toDegrees(cartographic.latitude);// 返回转换结果return [lon, lat];
};
3、计算中心点
// 计算中心点
const calcCenter = (value) => {// 计算中心点let pointArray = turf.polygon([value]);let center = turf.centerOfMass(pointArray);// 获取经纬度let lon = center.geometry.coordinates[0];let lat = center.geometry.coordinates[1];// 返回计算结果return { lon, lat };
};
4、计算角度
// 计算角度
const calcAngle = (value) => {// 将经纬度转换为笛卡尔坐标const cartesian1 = Cesium.Cartesian3.fromDegrees(value[0].lon, value[0].lat);const cartesian2 = Cesium.Cartesian3.fromDegrees(value[1].lon, value[1].lat);const cartesian3 = Cesium.Cartesian3.fromDegrees(value[2].lon, value[2].lat);// 构造向量(从顶点point2指向point1和point3)const vec1 = Cesium.Cartesian3.subtract(cartesian1,cartesian2,new Cesium.Cartesian3());const vec2 = Cesium.Cartesian3.subtract(cartesian3,cartesian2,new Cesium.Cartesian3());// 归一化向量(单位向量)Cesium.Cartesian3.normalize(vec1, vec1);Cesium.Cartesian3.normalize(vec2, vec2);// 计算点积const dot = Cesium.Cartesian3.dot(vec1, vec2);// 处理浮点误差(确保值在[-1, 1]范围内)const clampedDot = Math.min(Math.max(dot, -1.0), 1.0);// 计算夹角(弧度转角度)const angleRad = Math.acos(clampedDot);// 返回计算结果return Cesium.Math.toDegrees(angleRad);
};
三、测量函数
1、左键单击事件
// 左键单击
const leftClick = () => {// 添加用户输入监听范围(element)let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);// 处理用户输入事件handler.setInputAction((event) => {switch (indexStore.mapTools) {case 1: // 距离测量// 修改鼠标移动操作类型mouseMoveType.value = "calcLength";measureDistance(event);break;case 2: // 角度测量// 修改鼠标移动操作类型mouseMoveType.value = "calcAngle";measureAngle(event);break;case 3: // 面积测量// 修改鼠标移动操作类型mouseMoveType.value = "calcArea";measureArea(event);break;}}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
};
2、测量函数
// 距离测量
const measureDistance = (event) => {// 判断是否完成本次测量if (isFinishRang.value) {clearDistance();mouseMoveType.value = "calcLength";}// 屏幕坐标转经纬度let currentPosition = converted(event.position.x, event.position.y);let { lon, lat } = currentPosition;if (rangingObj.value.length == 0) {rangingObj.value.push(currentPosition);} else {// 保存测距点rangingObj.value.push(currentPosition);// 获取倒数第二位let beforePosition = rangingObj.value[rangingObj.value.length - 3];// 计算倒数第二位和当前位置的距离calcLength([beforePosition.lon, beforePosition.lat], [lon, lat]);}// 添加测距点addPoint(currentPosition, crypto.randomUUID(), "distance");// 绘制标签addLabel({ lon, lat, height: 200 },crypto.randomUUID(),"distance",`${totalRang.value.toFixed(2)}(公里)`);// 右键单击事件监听if (!rightClickEvent.value) {rightClickEvent.value = viewer.screenSpaceEventHandler.setInputAction((arg) => {let lastPosition = converted(arg.position.x, arg.position.y);let { lon, lat } = lastPosition;// 保存测距点rangingObj.value.push(lastPosition);// 获取倒数第二位let beforePosition = rangingObj.value[rangingObj.value.length - 3];// 计算倒数第二位和当前位置的距离calcLength([beforePosition.lon, beforePosition.lat], [lon, lat]);// 添加绘制addPoint(lastPosition, crypto.randomUUID(), "distance");// 绘制标签addLabel({ lon, lat, height: 200 },crypto.randomUUID(),"distance",`${totalRang.value.toFixed(2)}(公里)`);// 已完成本次测量isFinishRang.value = true;},Cesium.ScreenSpaceEventType.RIGHT_CLICK);}
};// 测量角度
const measureAngle = (event) => {// 判断是否完成本次测量if (isFinishAngle.value) {clearAngleDistance();mouseMoveType.value = "calcAngle";}// 屏幕坐标转经纬度let currentPosition = converted(event.position.x, event.position.y);// 保存点位angleObj.value.push(currentPosition);// 添加点位addPoint(currentPosition, crypto.randomUUID(), "distanceAngle");// 如果点数量>3结束绘制,并计算if (angleObj.value.length > 3) {// 删除最后一个点angleObj.value.pop();// -----计算三角形的中心点-----let temp = angleObj.value.flatMap((obj) => [[obj.lon, obj.lat]]);temp.push([angleObj.value[0].lon, angleObj.value[0].lat]);// 计算多边形的中心点let center = calcCenter(temp);// -----计算夹角角度-----const angleDeg = calcAngle(angleObj.value);// -----添加label标签-----addLabel({lon: center.lon,lat: center.lat,height: 10,},crypto.randomUUID(),`distanceAngle`,`${angleDeg}°`);// 清除鼠标移动操作类型mouseMoveType.value = null;// 已完成本次测量isFinishAngle.value = true;}
};// 测量面积
const measureArea = (event) => {// 判断是否完成了本次测量if (isFinishArea.value) {clearAreaDistance();mouseMoveType.value = "calcArea";}// 保存点位let { lon, lat } = converted(event.position.x, event.position.y);areaObj.value.push(Cesium.Cartesian3.fromDegrees(lon, lat));if (!findModelById("areaModel")) {modeClass.polygon.entities({id: "areaModel",outline: true,outlineColor: Cesium.Color.CYAN,outlineWidth: 20,height: 0,hierarchy: new Cesium.CallbackProperty(() => {return new Cesium.PolygonHierarchy(areaObj.value);}, false),material: Cesium.Color.CYAN.withAlpha(0.3),});}// 右键监听事件if (!rightClickEvent.value) {rightClickEvent.value = viewer.screenSpaceEventHandler.setInputAction((arg) => {// 计算中心点let temp = [];// 格式化点数据areaObj.value.forEach((item) => {temp.push(wordConverted(item.x, item.y, item.z));});// 最后一个点与第一个点相同temp.push(wordConverted(areaObj.value[0].x,areaObj.value[0].y,areaObj.value[0].z));// 计算多边形的中心点let center = calcCenter(temp);// 格式化数据let pointArray = turf.polygon([temp]);// 计算多边形面积,默认为平方米,转换为平方公里(除以1000000)let area = (turf.area(pointArray) / 1000000).toFixed(6);// 添加labeladdLabel({lon: center.lon,lat: center.lat,height: 10,},"areaModelLabel","areaModel",`${area}(平方公里)`);// 已完成本次测量isFinishArea.value = true;},Cesium.ScreenSpaceEventType.RIGHT_CLICK);}
};
3、鼠标移动事件
viewer.screenSpaceEventHandler.setInputAction((arg) => {switch (mouseMoveType.value) {case "calcLength": // 距离测量移动事件let calcPost = converted(arg.endPosition.x, arg.endPosition.y);if (rangingObj.value.length > 1) {rangingObj.value.pop();}rangingObj.value.push(calcPost);// 绘制线条if (rangingObj.value.length == 2) {modeClass.line.entities({id: crypto.randomUUID(),name: `distance`,// 参数依次为[经度1, 纬度1, 高度1, 经度2, 纬度2, 高度2]positions: new Cesium.CallbackProperty(() => {return Cesium.Cartesian3.fromDegreesArrayHeights(rangingObj.value.flatMap((obj) => [obj.lon, obj.lat, 0]));}, false),width: 4,material: new Cesium.PolylineOutlineMaterialProperty({color: Cesium.Color.TOMATO,outlineWidth: 2,outlineColor: Cesium.Color.YELLOW,}),clampToGround: false,distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0,150000000),});}break;case "calcAngle": // 角度测量移动事件let calcAngle = converted(arg.endPosition.x, arg.endPosition.y);if (angleObj.value.length > 1) {angleObj.value.pop();}// 保存点位angleObj.value.push(calcAngle);// 绘制线条if (angleObj.value.length == 2) {modeClass.line.entities({id: crypto.randomUUID(),name: `distanceAngle`,// 参数依次为[经度1, 纬度1, 高度1, 经度2, 纬度2, 高度2]positions: new Cesium.CallbackProperty(() => {return Cesium.Cartesian3.fromDegreesArrayHeights(angleObj.value.flatMap((obj) => [obj.lon, obj.lat, 0]));}, false),width: 4,material: new Cesium.PolylineOutlineMaterialProperty({color: Cesium.Color.TOMATO,outlineWidth: 2,outlineColor: Cesium.Color.YELLOW,}),clampToGround: false,distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0,150000000),});}break;case "calcArea": // 面积测量移动事件let areaPost = converted(arg.endPosition.x, arg.endPosition.y);if (areaObj.value.length > 1) {areaObj.value.pop();}areaObj.value.push(Cesium.Cartesian3.fromDegrees(areaPost.lon, areaPost.lat));break;}}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
4、清除测量函数
// 清除距离测量
const clearDistance = () => {// 清除距离测量右键单击事件viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);// 清除右键单击事件rightClickEvent.value = null;// 清空鼠标移动操作类型mouseMoveType.value = null;// 清除点位数据rangingObj.value = [];// 清理距离数据totalRang.value = 0;// 是否为新的测量,true为是,false为否isFinishRang.value = false;// 删除所有距离测量模型let entitiesArr = viewer.entities.values;for (let i = 0; i < entitiesArr.length; i++) {if (entitiesArr[i]._name == "distance") {removeModelById(entitiesArr[i]._id);i--;}}
};// 清除角度测量
const clearAngleDistance = () => {// 清除距离测量右键单击事件viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);// 清除右键单击事件rightClickEvent.value = null;// 清空鼠标移动操作类型mouseMoveType.value = null;// 是否为新的测量,true为是,false为否isFinishAngle.value = false;// 清除角度测量线条点位集合angleObj.value = [];// 清除角度测量点位集合anglePointObj.value = [];// 删除所有距离测量模型let entitiesArr = viewer.entities.values;for (let i = 0; i < entitiesArr.length; i++) {if (entitiesArr[i]._name == "distanceAngle") {removeModelById(entitiesArr[i]._id);i--;}}
};// 清除面积测量
const clearAreaDistance = () => {// 清除距离测量右键单击事件viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);// 清除右键单击事件rightClickEvent.value = null;// 清空鼠标移动操作类型mouseMoveType.value = null;// 是否为新的测量,true为是,false为否isFinishArea.value = false;// 清除面积点位集合areaObj.value = [];// 清除所有测试的模型removeModelById("areaModel");removeModelById("areaModelLabel");
};