Cesium 根据目标点和距离飞行

在 Cesium 三维可视化开发中,经常需要精确控制相机的位置和视角。本文将介绍如何根据目标点位置、相机与目标的距离、视角方向等参数,计算出相机的精确位置,并实现相机飞行定位的功能。

相机定位的核心问题

在 Cesium 中,直接设置相机位置通常需要提供世界坐标系中的绝对位置。但在实际应用中,我们往往更关心:

  1. 相机需要看向哪个点(目标点)
  2. 相机距离目标点多远(距离)
  3. 相机应该从什么角度观察目标(方位角和俯仰角)

这就需要我们编写一个函数,将这些更直观的参数转换为 Cesium 相机所需的绝对位置。

解决方案原理

我们的解决方案基于以下步骤:

  1. 以目标点为中心建立局部坐标系(东北上 ENU 坐标系)
  2. 在该坐标系中计算相机的相对位置
  3. 将相对位置转换回世界坐标系

这样就能准确定位相机,无论目标点在地球上的哪个位置。

核心函数解析

下面是计算相机位置的核心函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch) {
// 创建一个以目标点为中心的局部东北上(ENU)坐标系
var transform = Cesium.Transforms.eastNorthUpToFixedFrame(targetPosition);

// 计算相机的偏移量(初始位置在目标点正上方)
var offset = new Cesium.Cartesian3(0, 0, distanceFromTarget);

// 根据heading和pitch旋转偏移量
var rotation = Cesium.Matrix3.fromHeadingPitchRoll(
new Cesium.HeadingPitchRoll(-heading, pitch - Cesium.Math.PI_OVER_TWO, 0)
);
Cesium.Matrix3.multiplyByVector(rotation, offset, offset);

// 将偏移量从局部坐标系转换到全球坐标系
var cameraPosition = new Cesium.Cartesian3();
Cesium.Matrix4.multiplyByPoint(transform, offset, cameraPosition);

return cameraPosition;
}

关键步骤说明

  1. 创建局部坐标系:

    1
    var transform = Cesium.Transforms.eastNorthUpToFixedFrame(targetPosition);

    这一步创建了一个转换矩阵,将局部坐标系(东、北、上)与全球坐标系关联起来。

  2. 设置初始偏移:

    1
    var offset = new Cesium.Cartesian3(0, 0, distanceFromTarget);

    初始偏移设置为(0,0,distance),表示相机位于目标点正上方 distance 距离处。

  3. 计算旋转:

    1
    2
    3
    var rotation = Cesium.Matrix3.fromHeadingPitchRoll(
    new Cesium.HeadingPitchRoll(-heading, pitch - Cesium.Math.PI_OVER_TWO, 0)
    );

    这里创建一个旋转矩阵,注意参数的调整:

    • -heading:负号是因为 Cesium 中 heading 的方向定义
    • pitch - Cesium.Math.PI_OVER_TWO:减去 90 度是因为我们的初始位置在正上方
  4. 应用旋转:

    1
    Cesium.Matrix3.multiplyByVector(rotation, offset, offset);

    将旋转应用到偏移向量上。

  5. 转换到全球坐标:

    1
    Cesium.Matrix4.multiplyByPoint(transform, offset, cameraPosition);

    将局部坐标系中的偏移量转换为全球坐标系中的位置。

使用示例

了解了原理后,下面看看如何使用这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 目标点的位置(经度、纬度、高度)
var targetPosition = Cesium.Cartesian3.fromDegrees(116.391, 39.904, 50);

// 设置相机参数
var distanceFromTarget = 1000; // 相机距离目标点1000米
var heading = Cesium.Math.toRadians(45); // 方位角45度
var pitch = Cesium.Math.toRadians(30); // 俯仰角30度

// 计算相机位置
var cameraPosition = calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch);

// 设置相机视图
viewer.camera.setView({
destination: cameraPosition,
orientation: {
heading: heading,
pitch: pitch,
roll: 0,
},
});

这样设置后,相机会精确地位于目标点东北方向 1000 米处,并以 45 度方位角和 30 度俯仰角观察目标点。

应用场景

这种相机定位方法在许多场景中非常有用:

  1. 关键位置展示:快速定位到建筑物、地标等关键点的最佳观察位置
  2. 动画路径设计:创建围绕目标点的相机飞行路径
  3. 自定义导航控制:实现更直观的导航控制,如”环绕目标点旋转”、”拉近/拉远”等
  4. 多视角切换:在不同角度之间平滑过渡,展示目标的不同方面

完整代码

这里是完整的实现代码,包含了计算函数和使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 根据目标点、距离和视角计算相机位置
* @param {Cesium.Cartesian3} targetPosition 目标点的位置
* @param {Number} distanceFromTarget 相机到目标点的距离(米)
* @param {Number} heading 方位角(弧度)
* @param {Number} pitch 俯仰角(弧度)
* @returns {Cesium.Cartesian3} 计算出的相机位置
*/
function calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch) {
// 创建一个以目标点为中心的局部东北上(ENU)坐标系
var transform = Cesium.Transforms.eastNorthUpToFixedFrame(targetPosition);

// 计算相机的偏移量
var offset = new Cesium.Cartesian3(0, 0, distanceFromTarget);

// 根据heading和pitch旋转偏移量
var rotation = Cesium.Matrix3.fromHeadingPitchRoll(
new Cesium.HeadingPitchRoll(-heading, pitch - Cesium.Math.PI_OVER_TWO, 0)
);
Cesium.Matrix3.multiplyByVector(rotation, offset, offset);

// 将偏移量从局部坐标系转换到全球坐标系
var cameraPosition = new Cesium.Cartesian3();
Cesium.Matrix4.multiplyByPoint(transform, offset, cameraPosition);

return cameraPosition;
}

// 使用示例
function flyToPositionWithCamera() {
// 目标点的位置(北京天安门)
var targetPosition = Cesium.Cartesian3.fromDegrees(116.391, 39.904, 50);

// 设置相机参数
var distanceFromTarget = 1000; // 相机距离目标点1000米
var heading = Cesium.Math.toRadians(45); // 方位角45度
var pitch = Cesium.Math.toRadians(30); // 俯仰角30度

// 计算相机位置
var cameraPosition = calculateCameraPosition(targetPosition, distanceFromTarget, heading, pitch);

// 设置相机视图
viewer.camera.flyTo({
destination: cameraPosition,
orientation: {
heading: heading,
pitch: pitch,
roll: 0,
},
duration: 2, // 飞行时间,单位秒
});
}

// 调用函数
flyToPositionWithCamera();

总结

通过这种方法,我们可以更加直观地控制 Cesium 中的相机位置,无需直接处理复杂的世界坐标系。这对于创建精确的观察视角、设计相机动画路径等场景非常有用。

希望这篇文章对您在 Cesium 开发中控制相机有所帮助!

作者

wangyawei

发布于

2025-03-21

更新于

2025-03-21

许可协议