原文链接及内容

实现效果如下视频所示:

前言

本文将结合官方示例 Camera Tutorial 来入门学习 Cesium 中的 Camera,针对示例中用到的部分属性、方法做了注释和内容的补充。

实现思路

  1. 为了演示方便,首先,需要禁用Cesium的默认的时间处理程序,如下代码所示:如:scene.screenSpaceCameraController.enableLook = false,当该属性设置为为 true,则允许用户使用free-look,即可以通过鼠标自由拖动浏览。如果为 false,则只能通过平移或旋转来更改相机视图方向。

    1
    2
    3
    4
    5
    6
    //禁用默认事件处理程序
    scene.screenSpaceCameraController.enableRotate = false; //不允许旋转
    scene.screenSpaceCameraController.enableTranslate = false; //不允许移动地图,Camera将锁定在当前位置
    scene.screenSpaceCameraController.enableZoom = false; //不允许缩放,Camera锁定到与椭球体的当前距离
    scene.screenSpaceCameraController.enableTilt = false; //不允许Camera倾斜,Camera将锁定到当前航向
    scene.screenSpaceCameraController.enableLook = false; //如果为 true,则允许用户使用free-look。如果为 false,则只能通过平移或旋转来更改相机视图方向
  2. 然后,我们需要通过viewer.clock.onTick.addEventListener注册一个监听器,该监听器会在 Cesium 的时钟(Clock)更新时被调用,即执行ontick()方法,默认是 1 秒钟会被调用一次,该机制正好用来监听鼠标或键盘事件,以实现上述效果,通过键盘按键或鼠标来执行一些 Camera 的一些方法。

  3. 让后我们通过一些键盘的按键的keydownkeyup及鼠标的LEFT_DOWNMOUSE_MOVELEFT_UP等事件监听器来动态的调整flag的相关属性开关,如键盘按键按下时打开属性开关,键盘按键抬起时关闭属性开关,使得属性开关为 true 时才执行 Camera 相关方法。

代码实现

实现代码如下:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import useCSViewerStore from "@/stores/csViewer.js";
import {
ScreenSpaceEventHandler,
Cartesian3,
ScreenSpaceEventType,
defined,
} from "cesium";

const cvs = useCSViewerStore();
let viewer, scene, canvas, ellipsoid;
let handler;
let startMousePosition, mousePosition;
const flags = {
looking: false,
moveForward: false,
moveBackward: false,
moveUp: false,
moveDown: false,
moveLeft: false,
moveRight: false,
};

onMounted(() => {
initData();
initEvt();
});

function initData() {
viewer = cvs.viewer;
canvas = viewer.canvas;
canvas.setAttribute("tabindex", 0);

scene = viewer.scene;
ellipsoid = scene.globe.ellipsoid;
//禁用默认事件处理程序
scene.screenSpaceCameraController.enableRotate = false; //不允许旋转
scene.screenSpaceCameraController.enableTranslate = false; //不允许移动地图,Camera将锁定在当前位置
scene.screenSpaceCameraController.enableZoom = false; //不允许缩放,Camera锁定到与椭球体的当前距离
scene.screenSpaceCameraController.enableTilt = false; //不允许Camera倾斜,Camera将锁定到当前航向
scene.screenSpaceCameraController.enableLook = false; //如果为 true,则允许用户使用free-look。如果为 false,则只能通过平移或旋转来更改相机视图方向

handler = new ScreenSpaceEventHandler(canvas);
}

let onTick;
function initEvt() {
canvas.addEventListener("click", canvasListenerEvt);

handler.setInputAction((movement) => {
// console.log('movement: ', movement);
flags.looking = true;
mousePosition = startMousePosition = Cartesian3.clone(movement.position);
}, ScreenSpaceEventType.LEFT_DOWN);
handler.setInputAction((movement) => {
// console.log('movement: ', movement);
mousePosition = movement.endPosition;
}, ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction((position) => {
// console.log('position: ', position);
flags.looking = false;
}, ScreenSpaceEventType.LEFT_UP);

document.addEventListener("keydown", keyDownEvt);
document.addEventListener("keyup", keyUpEvt);

//每当调用 Clock#tick 时触发的 Event。参考:https://cesium.com/learn/cesiumjs/ref-doc/Clock.html?classFilter=clock#tick
onTick = viewer.clock.onTick.addEventListener(clockOntickEvt);
console.log("onTick: ", onTick);
}

//------------------要注册的事件-----------------------------
function canvasListenerEvt(e) {
// console.log("e: ", e);
canvas.focus();
}

function keyDownEvt(e) {
console.log("down", e.code);
setEvtFlag(e, true);
}

function keyUpEvt(e) {
console.log("up", e.code);
setEvtFlag(e, false);
}

function setEvtFlag(e, flag) {
const flagName = getFlagForKeyCode(e.code);
if (typeof flagName !== "undefined") {
flags[flagName] = flag;
}
}

function getFlagForKeyCode(code) {
switch (code) {
case "KeyW":
return "moveForward";
case "KeyS":
return "moveBackward";
case "KeyQ":
return "moveUp";
case "KeyE":
return "moveDown";
case "KeyD":
return "moveRight";
case "KeyA":
return "moveLeft";
default:
return undefined;
}
}

function clockOntickEvt(clock) {
// console.log("clock: ", clock);
const camera = viewer.camera;
if (flags.looking) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;

const x = (mousePosition.x - startMousePosition.x) / width;
const y = -(mousePosition.y - startMousePosition.y) / height;

const lookFactor = 0.05;
camera.lookRight(x * lookFactor);
camera.lookUp(y * lookFactor);
}

//根据摄像机到椭球体表面的距离更改移动速度。
const cameraHeight = ellipsoid.cartesianToCartographic(
camera.position
).height;
const moveRate = cameraHeight / 100.0;

if (flags.moveForward) {
camera.moveForward(moveRate);
}
if (flags.moveBackward) {
camera.moveBackward(moveRate);
}
if (flags.moveUp) {
camera.moveUp(moveRate);
}
if (flags.moveDown) {
camera.moveDown(moveRate);
}
if (flags.moveLeft) {
camera.moveLeft(moveRate);
}
if (flags.moveRight) {
camera.moveRight(moveRate);
}
}

//-------------解绑所有事件监听器------------
onBeforeUnmount(() => {
handler.removeInputAction(ScreenSpaceEventType.LEFT_DOWN);
handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
handler.removeInputAction(ScreenSpaceEventType.LEFT_UP);

document.removeEventListener("keydown", keyDownEvt);
document.removeEventListener("keyup", keyUpEvt);
canvas.removeEventListener("click", canvasListenerEvt);

if (defined(onTick)) {
//上面注册监听器的返回值为一个函数(Event.RemoveCallback),该函数将在调用时删除此事件监听器。
//具体可参考:https://cesium.com/learn/cesiumjs/ref-doc/Event.html#addEventListener
onTick(); //这里执行返回值函数则会删除此监听器
onTick = undefined;
}
});

经验总结

  1. Cesium 时钟在更新时始终会调用onTick()方法,这样使得监听器在每个渲染帧上都会被调用,基于该机制,我们可以创建一些动画和动态场景,例如飞行路径时间相关的数据可视化等。

  2. 记得在不需要的时候移除事件监听器,避免内存泄漏。

  3. Cesium 的 Event 对象的添加监听器方法是:addEventListener(listener, scope) → Event.RemoveCallback,其返回值是一个函数,执行该函数即可删除此事件监听器,看来官方的代码还是值得学习的。