Cesium距离测量、角度测量、面积测量

一、公共数据

javascript 复制代码
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、屏幕坐标转经纬度

javascript 复制代码
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、世界坐标转经纬度

javascript 复制代码
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、计算中心点

javascript 复制代码
// 计算中心点
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、计算角度

javascript 复制代码
// 计算角度
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、左键单击事件

javascript 复制代码
// 左键单击
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、测量函数

javascript 复制代码
// 距离测量
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);
        // 添加label
        addLabel(
          {
            lon: center.lon,
            lat: center.lat,
            height: 10,
          },
          "areaModelLabel",
          "areaModel",
          `${area}(平方公里)`
        );
        // 已完成本次测量
        isFinishArea.value = true;
      },
      Cesium.ScreenSpaceEventType.RIGHT_CLICK
    );
  }
};

3、鼠标移动事件

javascript 复制代码
  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、清除测量函数

javascript 复制代码
// 清除距离测量
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");
};
相关推荐
南囝coding14 分钟前
这个 361K Star 的项目,一定要收藏!
前端·后端·github
我不吃饼干15 分钟前
我给掘金写了一个给用户加标签的功能
前端·javascript·cursor
看到我,请让我去学习19 分钟前
C++核心编程(动态类型转换,STL,Lanmda)
c语言·开发语言·c++·stl
conkl26 分钟前
Apache网页优化实战指南 - 让网站加载速度提升
linux·运维·服务器·开发语言·阿里云·apache
羚羊角uou34 分钟前
【C++】模拟实现map和set
java·前端·c++
onlooker666635 分钟前
Go语言底层(五): 深入浅出Go语言的ants协程池
开发语言·后端·golang
刚子编程38 分钟前
C# WinForms 实现打印监听组件
开发语言·c#·winform·打印监听组件
泽02021 小时前
C++之模板进阶
开发语言·c++·算法
武子康1 小时前
Java-46 深入浅出 Tomcat 核心架构 Catalina 容器全解析 启动流程 线程机制
java·开发语言·spring boot·后端·spring·架构·tomcat
90后的晨仔1 小时前
ArkTS 与 Swift 闭包的对比分析
前端·harmonyos