cesium绘制路线,实现三维漫游

该代码示例展示了如何在Cesium三维地球中通过鼠标点击添加点和线,构建折线,并计算折线中心点。当右键结束绘制时,判断绘制方向。之后,实现点击飞行功能,包括设置飞行时间和动态调整相机视角。此外,还包含了计算两点间方位角的辅助函数。
摘要由CSDN通过智能技术生成

1.鼠标点击添加点和线

handler.setInputAction(function (event) {
        //返回地球表面上的点坐标
        const earthPosition = positionUtil.cartesian2ToCartesian3(event.position)
        if (Cesium.defined(earthPosition)) {
            const ellipsoid = viewer.scene.globe.ellipsoid;
            const cartographic = ellipsoid.cartesianToCartographic(earthPosition);
            const lon = Cesium.Math.toDegrees(cartographic.longitude);
            const lat = Cesium.Math.toDegrees(cartographic.latitude);
            const alt = cartographic.height + 20 // 高度加20
            AddPoint({
                lon,
                lat,
                alt,
                color: Cesium.Color.YELLOW
            }, viewer);
            positions.push(Cesium.Cartesian3.fromDegrees(lon, lat, alt))
            positons2.push({x:lon,y:lat,z:alt})
            if (positions.length >=2) {
              if (!polyline)
                polyline = AddPolyline(viewer)
            }
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    // 添加点
const AddPoint = (params, viewer) => {
  const entity:any = new Cesium.Entity({
      id:`${params.lon}`,
      name: '点',
      show: true,
      position: Cesium.Cartesian3.fromDegrees(params.lon, params.lat, params.alt),
      point: new Cesium.PointGraphics({
          show: true, //是否展示
          pixelSize: 10, //点大小
          color: params.color
      })
  });
  //收集Id
  viewer.entities.add(entity);
  return entity;
}
 // 添加线
 const AddPolyline = (viewer) => {
  const entity = new Cesium.Entity({
      id:`${new Date().getTime()}线`,
      name: '线',
      polyline: new Cesium.PolylineGraphics({
          show: true,
          clampToGround: false,
          positions: new Cesium.CallbackProperty(function () {
            return positions;
          }, false),//params.positions,
          width: 10,
          material: new Cesium.PolylineGlowMaterialProperty({
              glowPower: 0.2,
              color: Cesium.Color.BLUE
          })
      })
  } as any);
  viewer.entities.add(entity);
  return entity;
}

2.右键结束绘制时,计算折线中心点和绘制方向;折线中心点可以替换为模型中心点,这样就会看着这个点飞行;计算方向主要是判断绘制的顺序是顺时针绘制的还是逆时针绘制的

handler.setInputAction(function (event) {
      // 计算折线中心点
      centerPos = caclPolyCenter(viewer)
      isRight = _judgeDirection(positions)
      // console.log(isRight, '逆时针');
      if (handler) {
        handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
        handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
      }
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
    const caclPolyCenter = (viewer) => {
  const entity = new Cesium.Entity({
    name: "Red polygon on surface",
    polygon:{
      hierarchy: new Cesium.PolygonHierarchy(positions),
      material: Cesium.Color.RED.withAlpha(0.5),
    },
  });
  const polyPositions = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()).positions;
  let polyCenter = Cesium.BoundingSphere.fromPoints(polyPositions).center;//中心点
  polyCenter = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(polyCenter);
  const cartographic = Cesium.Cartographic.fromCartesian(polyCenter, viewer.scene.globe.ellipsoid, new Cesium.Cartographic());
  const lat = Cesium.Math.toDegrees(cartographic.latitude);
  const lng = Cesium.Math.toDegrees(cartographic.longitude);
  const height = cartographic.height;
  return {x: lng, y: lat, z: height}
}
/**
   * 判断点的顺序是顺时针还是逆时针
   * @param {Cesium.Cartesian3[]} points
   */
const _judgeDirection = (points) => {
  const lonlat1 = Cesium.Cartographic.fromCartesian(points[0])
  const lonlat2 = Cesium.Cartographic.fromCartesian(points[1])
  const lonlat3 = Cesium.Cartographic.fromCartesian(points[2])
  const x1 = lonlat1.longitude,
    y1 = lonlat1.latitude,
    x2 = lonlat2.longitude,
    y2 = lonlat2.latitude,
    x3 = lonlat3.longitude,
    y3 = lonlat3.latitude,
    dirRes = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2)

  const isR = dirRes > 0
  return isR
}

3.点击飞行
pitch默认为-5
setExtentTime: 在viewer的clock中设置时间间隔,flytime默认为15,也可以根据线段的长度来定义每一段的飞行时间
TimeExecution: 添加clock.onTick事件监听,每一帧调用,根据时间动态计算下一个点的坐标,然后更新相机位置.关于viewer.clock.onTick可查看api深入学习
changeCameraHeading: 如果当前的时间=设定的停止时间,移除监听并开始转向,当前时间=设定的停止时间时,开始下一段线的飞行marksIndex+1

const flyExtent = (viewer) => {
  // 相机看点的角度,如果大于0那么则是从地底往上看,所以要为负值
  const pitch = Cesium.Math.toRadians(pitchValue); //  默认-5
  // 时间间隔15秒钟
  setExtentTime(flytime, viewer);
  Exection = function TimeExecution() {
    let preIndex = marksIndex - 1;
    if (marksIndex === 0) {
      preIndex = positons2.length - 1;
    }
    //计算俯仰角

    // 当前已经过去的时间,单位s
    const delTime = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, viewer.clock.startTime);
    const originLat = marksIndex == 0 ? positons2[positons2.length - 1].y : positons2[marksIndex - 1].y;
    const originLng = marksIndex == 0 ? positons2[positons2.length - 1].x : positons2[marksIndex - 1].x;
    const endPosition = Cesium.Cartesian3.fromDegrees(
      (originLng + (positons2[marksIndex].x - originLng) / flytime * delTime),
      (originLat + (positons2[marksIndex].y - originLat) / flytime * delTime),
      positons2[marksIndex].z
    );
    let heading = bearing(originLat,originLng, centerPos.y, centerPos.x);
    // let heading = bearing(centerPos.y, centerPos.x, originLat,originLng);
    heading = Cesium.Math.toRadians(heading);
    viewer.scene.camera.setView({
      destination: endPosition,
      orientation: {
        heading: heading,
        pitch: pitch,
      }
    });
    if (Cesium.JulianDate.compare(viewer.clock.currentTime, viewer.clock.stopTime) >= 0) {
      viewer.clock.onTick.removeEventListener(Exection);
      //有个转向的功能
      changeCameraHeading(viewer);
    }
  };
  viewer.clock.onTick.addEventListener(Exection);
}
// 相机原地定点转向
const changeCameraHeading = (viewer) => {
  // 计算两点之间的方向
  let nextIndex = marksIndex + 1;
  if (marksIndex == positons2.length - 1) {
    nextIndex = 0;
  }
  const heading = bearing(positons2[marksIndex].y, positons2[marksIndex].x,centerPos.y, centerPos.x );
  console.log(heading, Cesium.Math.toDegrees(viewer.camera.heading));

  // 相机看点的角度,如果大于0那么则是从地底往上看,所以要为负值
  const pitch = Cesium.Math.toRadians(pitchValue);
  // 给定飞行一周所需时间,比如10s, 那么每秒转动度数
  // 相机是始终往角度小的方向转, 先确定绘制的线路是顺时针还是逆时针_judgeDirection
  // 判断当前camera.heading > 两点的heading
  // 如果大于,则360 - camera.heading + heading, eg: 360-340+20
  //这部分主要解决的是相机朝哪个方向转的问题,顺时针绘制的线和逆时针绘制的线转向不同,所以有了下面这些判断,或许还有更好的办法处理
  let angle = 0
  if (!isRight) { // 顺时针
    if (Cesium.Math.toDegrees(viewer.camera.heading) > heading) {
      angle = ((Cesium.Math.toDegrees(Cesium.Math.TWO_PI - viewer.camera.heading) + heading) / changeCameraTime);
    } else {
      angle = ((heading - Cesium.Math.toDegrees(viewer.camera.heading)) / changeCameraTime);
    }
  } else {
    if (Cesium.Math.toDegrees(viewer.camera.heading) < heading) {
      angle = ((Cesium.Math.toDegrees(Cesium.Math.toRadians(heading) - Cesium.Math.TWO_PI) - Cesium.Math.toDegrees(viewer.camera.heading)) / changeCameraTime);
    } else {
      angle = ((heading - Cesium.Math.toDegrees(viewer.camera.heading)) / changeCameraTime);
    }
    // angle = ((heading - Cesium.Math.toDegrees(viewer.camera.heading)) / changeCameraTime);
  }
  // const angle = ((heading - Cesium.Math.toDegrees(viewer.camera.heading)) / changeCameraTime);
  console.log(angle, 'angle');
  // 时间间隔2秒钟
  setExtentTime(changeCameraTime, viewer);
  // 相机的当前heading
  const initialHeading = viewer.camera.heading;
  Exection = function TimeExecution() {
    // 当前已经过去的时间,单位s
    const delTime = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, viewer.clock.startTime);
    // console.log(Cesium.Math.toRadians(delTime * angle), '1s转动的角度');

    const heading = Cesium.Math.toRadians(delTime * angle) + initialHeading;
    viewer.scene.camera.setView({
      orientation: {
        heading: heading,
        pitch: pitch
      }
    });
    if (Cesium.JulianDate.compare(viewer.clock.currentTime, viewer.clock.stopTime) >= 0) {
      viewer.clock.onTick.removeEventListener(Exection);
      marksIndex = ++marksIndex >= positons2.length ? 0 : marksIndex;
      if (marksIndex != 0) {
        console.log(marksIndex, 'marksIndex');
        flyExtent(viewer);
      }
    }
  };
  viewer.clock.onTick.addEventListener(Exection);
}
//计算俯仰角
const bearing = (startLat, startLng, destLat, destLng) => {
  startLat = toRadians(startLat);
  startLng = toRadians(startLng);
  destLat = toRadians(destLat);
  destLng = toRadians(destLng);

  const y = Math.sin(destLng - startLng) * Math.cos(destLat);
  const x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
  const brng = Math.atan2(y, x);
  const brngDgr = toDegrees(brng);

  return (brngDgr + 360) % 360;
}
// 角度转弧度
const toRadians = (degrees) => {
  return degrees * Math.PI / 180;
}
// 弧度转角度
const toDegrees = (radians) => {
  return radians * 180 / Math.PI;
}

desktop 2023-04-03 11-55-18

参考大佬文章: https://blog.csdn.net/CFXXXL/article/details/125365952

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值