Cesium 加载GPX定位飞行到实体不精准问题

最近遇到一个需求,当加载gpx后,需要将视角定位到实体的合适位置。刚开始想这不是很简单嘛,直接使用zoomTo flyTo 跳转到Gpx实体就好了,于是开始了尝试。

尝试后发现了问题,当gpx范围很小时,使用zoomTo flyTo等定位过去后视角离实体很远,看不清细节,这就不是我们能够接受的,于是就开始寻找替代方法。

于是我想到了使用BoundingSphere手动创建一个边界球,再定位到这个边界球,这样就变相解决了定位的问题,并且viewer.camera.flyToBoundingSphere定位时还会自动调整合适的俯仰角。

BoundingSphere 需要两个参数 一个是坐标点,一个是球半径。坐标点我们可以根据gpx实体中的polyline中获取,然后计算其最大外边界矩形,再计算矩形的中心点,及其最大最远两个点的距离(球直径)。如此我们就获取到两个核心参数了。

这里直接上代码。

这里用的turf

npm i @turf/turf
/**
 * 根据几何图形计算中心点 最大矩形及其半径
 *
 * @export
 * @param {*} geometry
 * @return {{
 * center?:[number,number],
 * radius?:number
 * }}
 */
function getCenterAndRadius(geometry) {
  // 计算边界
  const bbox = turf.bbox(geometry);
  // 计算最大边界矩形
  const squared = turf.square(bbox);
  // 计算半径
  const features = turf.points([
    [squared[0], squared[1]],
    [squared[2], squared[3]],
  ]);
  const center = turf.center(features);
  const from = turf.point([squared[0], squared[1]]);
  const to = turf.point([squared[2], squared[3]]);
  const options = { units: 'kilometers' };//公里

  const distance = turf.distance(from, to, options);

  return {
    // 这里÷2是因为计算的值为直径,而边界球需要半径
    radius: distance * 1000 / 2,
    center: center.geometry?.coordinates,
  };
}

/**
 *
 * 自定义 flyToPoint
 * @export
 * @param {Viewer} viewer 地图实例
 * @param {{
 * longitude: number;
 * latitude: number;
 * radius?: number;
 * complete:Function;
 * }} option 配置项
 * @param {*} option.longitude
 * @param {*} option.latitude
 * @param {Function} option.complete 飞行结束后执行的回调
 * @param {number} option.height 球高度
 * @param {number} [option.radius=1000] 值越大 看到范围越大。反之缩小
 */
export function customFlyToPoint(viewer = map, option) {
  if (!viewer) return;

  const { longitude, latitude, radius = 1000, complete } = option;

  viewer.camera.flyToBoundingSphere(
    // 边界盒
    new Cesium.BoundingSphere(
      Cesium.Cartesian3.fromDegrees(longitude, latitude, Math.abs(getHeightFast(viewer, longitude, latitude))),
      radius,
    ),
    {
      duration: 4, // 飞行时间(秒)
      // pitchAdjustHeight: 4000,
      complete: complete,
    },
  );
  // 这里可以查看边界盒 中心点
  // viewer.entities.add({
  //   position: Cesium.Cartesian3.fromDegrees(longitude, latitude, Math.abs(getHeightFast(viewer, longitude, latitude))),

  //   point: {
  //     pixelSize: 50,
  //     color: Cesium.Color.fromCssColorString('#ff0'),
  //     outline: true,
  //     outlineColor: Cesium.Color.fromCssColorString('#000'),
  //     outlineWidth: 2,
  //   },
  // });
}

viewer.dataSources
  .add(
    Cesium.GpxDataSource.load('../SampleData/gpx/route.gpx', {
      clampToGround: true,
    }),
  )
  .then(function (dataSource) {
    let positions = [];

    // 设置所有 polyline 的宽度
    dataSource.entities.values.forEach((entity) => {
      if (entity.polyline && entity.polyline.positions) {
        entity.polyline.width = 10;

        const polylinePositions = entity.polyline.positions.getValue(Cesium.JulianDate.now());

        if (polylinePositions && Array.isArray(polylinePositions)) {
          positions.push(...polylinePositions);
        }
      }
    });
    // Cesium.Cartesian3 转为 经纬度
    positions = positions.map((item) => {
      const ellipsoid = Cesium.Ellipsoid.WGS84; // 使用 WGS84 椭球体模型
      const cartographic = ellipsoid.cartesianToCartographic(item);
      const longitude = Cesium.Math.toDegrees(cartographic.longitude);
      const latitude = Cesium.Math.toDegrees(cartographic.latitude);
      const height = Cesium.Math.toDegrees(cartographic.height);

      return [longitude, latitude, height];
    });
    const line = turf.lineString(arr2);
    const { center, radius } = getCenterAndRadius(line);

    customFlyToPoint(map, {
      latitude: center[1],
      longitude: center[0],
      radius: radius,
      complete: () => {
        // 定位完成后回调
      },
    });

  });

到这里就基本完成了,但是 我在测试的时候发现在计算高度时,高度的值十分奇怪,致使该效果未达到我预期,变得时灵时不灵。

function getHeightFast(viewer, longitude, latitude) {
  const cartographic = Cesium.Cartographic.fromDegrees(longitude, latitude);
  const height = viewer.scene.globe.getHeight(cartographic);

  return height;
}

这个方法根据当前地形拾取而来。我就有了一点猜想,可能是因为在gpx加载时,此时视角并不在gpx实体的区域,此时gpx实体区域的地形也未加载,我通过这个方法获取高度,就给了我一个不正确的值。于是我将代码进行了细微的修改

下面是更改的部分

......
// 前面代码未改动
viewer.dataSources
  .add(
    Cesium.GpxDataSource.load('你的gpx文件地址', {
      clampToGround: true,
    }),
  )
  .then(function (dataSource) {
    let positions = [];

    // 设置所有 polyline 的宽度
    dataSource.entities.values.forEach((entity) => {
      if (entity.polyline && entity.polyline.positions) {
        entity.polyline.width = 10;
        // 获取gpx所有点位
        const polylinePositions = entity.polyline.positions.getValue(Cesium.JulianDate.now());

        if (polylinePositions && Array.isArray(polylinePositions)) {
          positions.push(...polylinePositions);
        }
      }
    });
    // Cesium.Cartesian3 转为 经纬度
    positions = positions.map((item) => {
      const ellipsoid = Cesium.Ellipsoid.WGS84; // 使用 WGS84 椭球体模型
      const cartographic = ellipsoid.cartesianToCartographic(item);
      const longitude = Cesium.Math.toDegrees(cartographic.longitude);
      const latitude = Cesium.Math.toDegrees(cartographic.latitude);
      const height = Cesium.Math.toDegrees(cartographic.height);

      return [longitude, latitude, height];
    });
    const line = turf.lineString(arr2);
    const { center, radius } = getCenterAndRadius(line);
    // 这里先跳转至gpx实体,加载地形再调用方法从地形上获取高度
    viewer.zoomTo(dataSource.entities).then(() => {
      // 检查是否收集到有效位置
      if (positions.length === 0) {
        console.error('No valid positions found in entities');

        return;
      }

      customFlyToPoint(map, {
        latitude: center[1],
        longitude: center[0],
        radius: radius,
        complete: () => {
          // 定位完成后回调
        },
      });
    });
  });

完美解决

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值