第05章 - 相机系统与视角控制
第05章:相机系统与视角控制
5.1 相机基础概念
5.1.1 相机坐标系
CesiumJS 中的相机使用多种坐标系来描述其位置和方向:
┌─────────────────────────────────────────────────────────────────┐
│ 相机坐标系统 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 世界坐标系 (World) │
│ ├── 以地球中心为原点 │
│ ├── X 轴指向经度 0°、纬度 0° 方向 │
│ ├── Y 轴指向经度 90°E、纬度 0° 方向 │
│ └── Z 轴指向北极 │
│ │
│ 相机坐标系 (Camera) │
│ ├── position: 相机在世界坐标系中的位置 │
│ ├── direction: 相机观察方向(单位向量) │
│ ├── up: 相机上方向(单位向量) │
│ └── right: 相机右方向(单位向量) │
│ │
│ 方位角系统 (Orientation) │
│ ├── heading: 航向角(方位角,北向为 0,顺时针增加) │
│ ├── pitch: 俯仰角(水平为 0,向下为负,向上为正) │
│ └── roll: 翻滚角(绕视线轴旋转) │
│ │
└─────────────────────────────────────────────────────────────────┘
5.1.2 相机属性
const camera = viewer.camera;
// ===== 位置属性 =====
camera.position // Cartesian3 - 世界坐标位置
camera.positionWC // Cartesian3 - 世界坐标(只读)
camera.positionCartographic // Cartographic - 经纬度位置
// ===== 方向属性 =====
camera.direction // Cartesian3 - 观察方向
camera.directionWC // Cartesian3 - 世界坐标观察方向(只读)
camera.up // Cartesian3 - 上方向
camera.upWC // Cartesian3 - 世界坐标上方向(只读)
camera.right // Cartesian3 - 右方向
camera.rightWC // Cartesian3 - 世界坐标右方向(只读)
// ===== 方位角属性 =====
camera.heading // Number - 航向角(弧度)
camera.pitch // Number - 俯仰角(弧度)
camera.roll // Number - 翻滚角(弧度)
// ===== 视锥体属性 =====
camera.frustum // PerspectiveFrustum - 透视视锥体
camera.frustum.fov // Number - 视场角
camera.frustum.aspectRatio // Number - 宽高比
camera.frustum.near // Number - 近裁剪面
camera.frustum.far // Number - 远裁剪面
// ===== 其他属性 =====
camera.defaultMoveAmount // Number - 默认移动量
camera.defaultLookAmount // Number - 默认旋转量
camera.defaultRotateAmount // Number - 默认自转量
camera.defaultZoomAmount // Number - 默认缩放量
camera.constrainedAxis // Cartesian3 - 约束轴
5.2 相机定位方法
5.2.1 setView - 直接设置视角
// 基本用法
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 15000),
orientation: {
heading: Cesium.Math.toRadians(0), // 朝北
pitch: Cesium.Math.toRadians(-45), // 向下倾斜 45°
roll: 0
}
});
// 使用 Rectangle 定位到区域
viewer.camera.setView({
destination: Cesium.Rectangle.fromDegrees(
116.0, 39.0, // 西南角
117.0, 40.0 // 东北角
)
});
// 带偏移的定位
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 0),
orientation: new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(90), // 朝东
Cesium.Math.toRadians(-30), // 向下 30°
0
)
});
// 使用方向向量
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 15000),
orientation: {
direction: new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
up: new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
}
});
5.2.2 flyTo - 飞行动画
// 基本飞行
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 15000),
duration: 3 // 飞行时间(秒)
});
// 完整配置
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 15000),
orientation: {
heading: Cesium.Math.toRadians(45),
pitch: Cesium.Math.toRadians(-45),
roll: 0
},
duration: 3, // 飞行时间
complete: function() { // 完成回调
console.log('飞行完成');
},
cancel: function() { // 取消回调
console.log('飞行取消');
},
endTransform: Cesium.Matrix4.IDENTITY, // 结束变换矩阵
maximumHeight: 30000, // 最大飞行高度
pitchAdjustHeight: 1000, // 俯仰调整高度
flyOverLongitude: undefined, // 飞越经度
flyOverLongitudeWeight: 0.5, // 飞越经度权重
convert: true, // 是否转换坐标
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT // 缓动函数
});
// 飞行到矩形区域
viewer.camera.flyTo({
destination: Cesium.Rectangle.fromDegrees(116.0, 39.0, 117.0, 40.0),
duration: 2
});
// 使用 Promise
const flightPromise = viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 15000)
});
5.2.3 lookAt - 观察目标
// 观察一个点
const target = Cesium.Cartesian3.fromDegrees(116.4, 39.9, 0);
const offset = new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(45), // 从东北方向观察
Cesium.Math.toRadians(-30), // 向下 30°
10000 // 距离目标 10km
);
viewer.camera.lookAt(target, offset);
// 使用 Cartesian3 偏移
const offsetCartesian = new Cesium.Cartesian3(5000, 5000, 10000);
viewer.camera.lookAt(target, offsetCartesian);
// 持续跟踪(锁定视角)
viewer.camera.lookAtTransform(
Cesium.Transforms.eastNorthUpToFixedFrame(target),
offset
);
// 解除锁定
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
5.2.4 flyToBoundingSphere - 飞行到边界球
// 计算边界球
const positions = Cesium.Cartesian3.fromDegreesArray([
116.0, 39.0,
117.0, 39.0,
117.0, 40.0,
116.0, 40.0
]);
const boundingSphere = Cesium.BoundingSphere.fromPoints(positions);
// 飞行到边界球
viewer.camera.flyToBoundingSphere(boundingSphere, {
duration: 2,
offset: new Cesium.HeadingPitchRange(0, -0.5, 0) // 0 表示自动计算距离
});
5.3 相机移动与旋转
5.3.1 移动方法
const camera = viewer.camera;
// ===== 平移移动 =====
camera.move(direction, amount); // 沿指定方向移动
camera.moveForward(amount); // 向前移动
camera.moveBackward(amount); // 向后移动
camera.moveLeft(amount); // 向左移动
camera.moveRight(amount); // 向右移动
camera.moveUp(amount); // 向上移动
camera.moveDown(amount); // 向下移动
// 示例:向前移动 1000 米
camera.moveForward(1000);
// ===== 缩放移动 =====
camera.zoomIn(amount); // 放大
camera.zoomOut(amount); // 缩小
// 示例:放大 5000 米
camera.zoomIn(5000);
5.3.2 旋转方法
const camera = viewer.camera;
// ===== 环视旋转(改变观察方向)=====
camera.look(axis, angle); // 绕指定轴旋转
camera.lookLeft(amount); // 向左看
camera.lookRight(amount); // 向右看
camera.lookUp(amount); // 向上看
camera.lookDown(amount); // 向下看
// ===== 自转旋转(绕相机位置旋转)=====
camera.rotate(axis, angle); // 绕指定轴自转
camera.rotateLeft(angle); // 向左自转
camera.rotateRight(angle); // 向右自转
camera.rotateUp(angle); // 向上自转
camera.rotateDown(angle); // 向下自转
// ===== 扭转旋转(改变 heading/pitch/roll)=====
camera.twistLeft(amount); // 向左扭转
camera.twistRight(amount); // 向右扭转
// 示例:向右旋转 10°
camera.rotateRight(Cesium.Math.toRadians(10));
5.3.3 组合移动示例
// 实现键盘控制相机
class CameraKeyboardController {
constructor(viewer) {
this.viewer = viewer;
this.camera = viewer.camera;
this.flags = {
moveForward: false,
moveBackward: false,
moveLeft: false,
moveRight: false,
moveUp: false,
moveDown: false,
lookLeft: false,
lookRight: false,
lookUp: false,
lookDown: false
};
this.moveSpeed = 100;
this.rotateSpeed = 0.01;
this.initKeyEvents();
this.startLoop();
}
initKeyEvents() {
document.addEventListener('keydown', (e) => this.handleKeyDown(e));
document.addEventListener('keyup', (e) => this.handleKeyUp(e));
}
handleKeyDown(e) {
switch (e.key.toLowerCase()) {
case 'w': this.flags.moveForward = true; break;
case 's': this.flags.moveBackward = true; break;
case 'a': this.flags.moveLeft = true; break;
case 'd': this.flags.moveRight = true; break;
case 'q': this.flags.moveUp = true; break;
case 'e': this.flags.moveDown = true; break;
case 'arrowleft': this.flags.lookLeft = true; break;
case 'arrowright': this.flags.lookRight = true; break;
case 'arrowup': this.flags.lookUp = true; break;
case 'arrowdown': this.flags.lookDown = true; break;
}
}
handleKeyUp(e) {
switch (e.key.toLowerCase()) {
case 'w': this.flags.moveForward = false; break;
case 's': this.flags.moveBackward = false; break;
case 'a': this.flags.moveLeft = false; break;
case 'd': this.flags.moveRight = false; break;
case 'q': this.flags.moveUp = false; break;
case 'e': this.flags.moveDown = false; break;
case 'arrowleft': this.flags.lookLeft = false; break;
case 'arrowright': this.flags.lookRight = false; break;
case 'arrowup': this.flags.lookUp = false; break;
case 'arrowdown': this.flags.lookDown = false; break;
}
}
startLoop() {
this.viewer.clock.onTick.addEventListener(() => {
if (this.flags.moveForward) this.camera.moveForward(this.moveSpeed);
if (this.flags.moveBackward) this.camera.moveBackward(this.moveSpeed);
if (this.flags.moveLeft) this.camera.moveLeft(this.moveSpeed);
if (this.flags.moveRight) this.camera.moveRight(this.moveSpeed);
if (this.flags.moveUp) this.camera.moveUp(this.moveSpeed);
if (this.flags.moveDown) this.camera.moveDown(this.moveSpeed);
if (this.flags.lookLeft) this.camera.lookLeft(this.rotateSpeed);
if (this.flags.lookRight) this.camera.lookRight(this.rotateSpeed);
if (this.flags.lookUp) this.camera.lookUp(this.rotateSpeed);
if (this.flags.lookDown) this.camera.lookDown(this.rotateSpeed);
});
}
}
// 使用
const controller = new CameraKeyboardController(viewer);
5.4 相机动画
5.4.1 缓动函数
// CesiumJS 内置的缓动函数
const easingFunctions = {
// 线性
LINEAR_NONE: Cesium.EasingFunction.LINEAR_NONE,
// 二次方
QUADRATIC_IN: Cesium.EasingFunction.QUADRATIC_IN,
QUADRATIC_OUT: Cesium.EasingFunction.QUADRATIC_OUT,
QUADRATIC_IN_OUT: Cesium.EasingFunction.QUADRATIC_IN_OUT,
// 三次方
CUBIC_IN: Cesium.EasingFunction.CUBIC_IN,
CUBIC_OUT: Cesium.EasingFunction.CUBIC_OUT,
CUBIC_IN_OUT: Cesium.EasingFunction.CUBIC_IN_OUT,
// 四次方
QUARTIC_IN: Cesium.EasingFunction.QUARTIC_IN,
QUARTIC_OUT: Cesium.EasingFunction.QUARTIC_OUT,
QUARTIC_IN_OUT: Cesium.EasingFunction.QUARTIC_IN_OUT,
// 五次方
QUINTIC_IN: Cesium.EasingFunction.QUINTIC_IN,
QUINTIC_OUT: Cesium.EasingFunction.QUINTIC_OUT,
QUINTIC_IN_OUT: Cesium.EasingFunction.QUINTIC_IN_OUT,
// 正弦
SINUSOIDAL_IN: Cesium.EasingFunction.SINUSOIDAL_IN,
SINUSOIDAL_OUT: Cesium.EasingFunction.SINUSOIDAL_OUT,
SINUSOIDAL_IN_OUT: Cesium.EasingFunction.SINUSOIDAL_IN_OUT,
// 指数
EXPONENTIAL_IN: Cesium.EasingFunction.EXPONENTIAL_IN,
EXPONENTIAL_OUT: Cesium.EasingFunction.EXPONENTIAL_OUT,
EXPONENTIAL_IN_OUT: Cesium.EasingFunction.EXPONENTIAL_IN_OUT,
// 圆形
CIRCULAR_IN: Cesium.EasingFunction.CIRCULAR_IN,
CIRCULAR_OUT: Cesium.EasingFunction.CIRCULAR_OUT,
CIRCULAR_IN_OUT: Cesium.EasingFunction.CIRCULAR_IN_OUT,
// 弹性
ELASTIC_IN: Cesium.EasingFunction.ELASTIC_IN,
ELASTIC_OUT: Cesium.EasingFunction.ELASTIC_OUT,
ELASTIC_IN_OUT: Cesium.EasingFunction.ELASTIC_IN_OUT,
// 回弹
BACK_IN: Cesium.EasingFunction.BACK_IN,
BACK_OUT: Cesium.EasingFunction.BACK_OUT,
BACK_IN_OUT: Cesium.EasingFunction.BACK_IN_OUT,
// 弹跳
BOUNCE_IN: Cesium.EasingFunction.BOUNCE_IN,
BOUNCE_OUT: Cesium.EasingFunction.BOUNCE_OUT,
BOUNCE_IN_OUT: Cesium.EasingFunction.BOUNCE_IN_OUT
};
// 使用缓动函数
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 15000),
duration: 3,
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT
});
5.4.2 自定义相机动画
// 相机环绕动画
function orbitAnimation(viewer, target, duration = 10) {
const startTime = viewer.clock.currentTime;
const endTime = Cesium.JulianDate.addSeconds(startTime, duration, new Cesium.JulianDate());
function tick() {
const currentTime = viewer.clock.currentTime;
const elapsed = Cesium.JulianDate.secondsDifference(currentTime, startTime);
if (elapsed >= duration) {
return;
}
const angle = (elapsed / duration) * 2 * Math.PI;
const heading = Cesium.Math.toRadians(angle * 180 / Math.PI);
viewer.camera.lookAt(
target,
new Cesium.HeadingPitchRange(heading, Cesium.Math.toRadians(-30), 10000)
);
requestAnimationFrame(tick);
}
tick();
}
// 使用
const target = Cesium.Cartesian3.fromDegrees(116.4, 39.9, 0);
orbitAnimation(viewer, target, 20);
// 相机路径动画
async function flyAlongPath(viewer, waypoints, speed = 1000) {
for (let i = 0; i < waypoints.length - 1; i++) {
const start = waypoints[i];
const end = waypoints[i + 1];
const distance = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromDegrees(start.lon, start.lat, start.height),
Cesium.Cartesian3.fromDegrees(end.lon, end.lat, end.height)
);
const duration = distance / speed;
await new Promise(resolve => {
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(end.lon, end.lat, end.height),
orientation: {
heading: Cesium.Math.toRadians(end.heading || 0),
pitch: Cesium.Math.toRadians(end.pitch || -30),
roll: 0
},
duration: duration,
complete: resolve
});
});
}
}
// 使用
const waypoints = [
{ lon: 116.4, lat: 39.9, height: 15000, heading: 0, pitch: -30 },
{ lon: 121.5, lat: 31.2, height: 10000, heading: 45, pitch: -45 },
{ lon: 113.2, lat: 23.1, height: 8000, heading: 90, pitch: -20 }
];
flyAlongPath(viewer, waypoints, 500);
5.4.3 平滑过渡动画
// 平滑缩放动画
function smoothZoom(viewer, targetHeight, duration = 1) {
const camera = viewer.camera;
const startHeight = camera.positionCartographic.height;
const startTime = Date.now();
function animate() {
const elapsed = (Date.now() - startTime) / 1000;
const progress = Math.min(elapsed / duration, 1);
// 使用缓动函数
const easedProgress = Cesium.EasingFunction.CUBIC_IN_OUT(progress);
const currentHeight = startHeight + (targetHeight - startHeight) * easedProgress;
const cartographic = camera.positionCartographic;
camera.setView({
destination: Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
currentHeight
)
});
if (progress < 1) {
requestAnimationFrame(animate);
}
}
animate();
}
// 使用
smoothZoom(viewer, 5000, 2); // 缩放到 5km 高度,2秒完成
5.5 相机事件
5.5.1 相机事件监听
const camera = viewer.camera;
// ===== 移动事件 =====
camera.moveStart.addEventListener(function() {
console.log('相机开始移动');
});
camera.moveEnd.addEventListener(function() {
console.log('相机停止移动');
});
// ===== 变化事件 =====
camera.changed.addEventListener(function(percentage) {
console.log('相机变化:', percentage);
// 获取当前位置
const position = camera.positionCartographic;
console.log('当前位置:', {
longitude: Cesium.Math.toDegrees(position.longitude),
latitude: Cesium.Math.toDegrees(position.latitude),
height: position.height
});
});
// 设置变化阈值
camera.percentageChanged = 0.1; // 变化 10% 触发事件
5.5.2 相机状态监控
// 相机状态监控器
class CameraMonitor {
constructor(viewer) {
this.viewer = viewer;
this.camera = viewer.camera;
this.lastPosition = null;
this.callbacks = {
onMove: [],
onZoom: [],
onRotate: []
};
this.startMonitoring();
}
startMonitoring() {
this.viewer.scene.postRender.addEventListener(() => {
const currentPosition = this.camera.positionCartographic.clone();
if (this.lastPosition) {
const heightChange = Math.abs(currentPosition.height - this.lastPosition.height);
const latChange = Math.abs(currentPosition.latitude - this.lastPosition.latitude);
const lonChange = Math.abs(currentPosition.longitude - this.lastPosition.longitude);
if (heightChange > 1) {
this.callbacks.onZoom.forEach(cb => cb(currentPosition.height));
}
if (latChange > 0.0001 || lonChange > 0.0001) {
this.callbacks.onMove.forEach(cb => cb({
longitude: Cesium.Math.toDegrees(currentPosition.longitude),
latitude: Cesium.Math.toDegrees(currentPosition.latitude)
}));
}
}
this.lastPosition = currentPosition;
});
}
onMove(callback) {
this.callbacks.onMove.push(callback);
}
onZoom(callback) {
this.callbacks.onZoom.push(callback);
}
getCurrentState() {
const cartographic = this.camera.positionCartographic;
return {
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
height: cartographic.height,
heading: Cesium.Math.toDegrees(this.camera.heading),
pitch: Cesium.Math.toDegrees(this.camera.pitch),
roll: Cesium.Math.toDegrees(this.camera.roll)
};
}
}
// 使用
const monitor = new CameraMonitor(viewer);
monitor.onMove(pos => console.log('移动到:', pos));
monitor.onZoom(height => console.log('高度:', height));
5.6 视角控制高级功能
5.6.1 视角限制
// 限制相机移动范围
function limitCameraExtent(viewer, extent) {
const west = Cesium.Math.toRadians(extent.west);
const south = Cesium.Math.toRadians(extent.south);
const east = Cesium.Math.toRadians(extent.east);
const north = Cesium.Math.toRadians(extent.north);
viewer.scene.postRender.addEventListener(function() {
const camera = viewer.camera;
const position = camera.positionCartographic;
let needsUpdate = false;
let newLon = position.longitude;
let newLat = position.latitude;
if (position.longitude < west) {
newLon = west;
needsUpdate = true;
} else if (position.longitude > east) {
newLon = east;
needsUpdate = true;
}
if (position.latitude < south) {
newLat = south;
needsUpdate = true;
} else if (position.latitude > north) {
newLat = north;
needsUpdate = true;
}
if (needsUpdate) {
camera.setView({
destination: Cesium.Cartesian3.fromRadians(
newLon, newLat, position.height
),
orientation: {
heading: camera.heading,
pitch: camera.pitch,
roll: camera.roll
}
});
}
});
}
// 使用:限制在中国范围内
limitCameraExtent(viewer, {
west: 73.0,
south: 3.0,
east: 135.0,
north: 54.0
});
// 限制缩放范围
const controller = viewer.scene.screenSpaceCameraController;
controller.minimumZoomDistance = 100; // 最近 100 米
controller.maximumZoomDistance = 20000000; // 最远 20000 公里
5.6.2 第一人称视角
// 第一人称漫游模式
class FirstPersonController {
constructor(viewer) {
this.viewer = viewer;
this.camera = viewer.camera;
this.moveSpeed = 10;
this.turnSpeed = 0.002;
this.height = 1.7; // 人眼高度
this.enabled = false;
this.handler = null;
this.initMouseHandler();
}
enable() {
this.enabled = true;
// 禁用默认控制
const controller = this.viewer.scene.screenSpaceCameraController;
controller.enableRotate = false;
controller.enableTranslate = false;
controller.enableZoom = false;
controller.enableTilt = false;
controller.enableLook = false;
// 设置初始视角
const cartographic = this.camera.positionCartographic;
this.camera.setView({
destination: Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
this.height
),
orientation: {
heading: this.camera.heading,
pitch: 0,
roll: 0
}
});
}
disable() {
this.enabled = false;
// 恢复默认控制
const controller = this.viewer.scene.screenSpaceCameraController;
controller.enableRotate = true;
controller.enableTranslate = true;
controller.enableZoom = true;
controller.enableTilt = true;
controller.enableLook = true;
}
initMouseHandler() {
this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);
let lastX = 0;
let lastY = 0;
this.handler.setInputAction((movement) => {
if (!this.enabled) return;
const deltaX = movement.endPosition.x - movement.startPosition.x;
const deltaY = movement.endPosition.y - movement.startPosition.y;
// 水平旋转
this.camera.setView({
destination: this.camera.position,
orientation: {
heading: this.camera.heading - deltaX * this.turnSpeed,
pitch: Math.max(
Math.min(this.camera.pitch - deltaY * this.turnSpeed, Math.PI / 2),
-Math.PI / 2
),
roll: 0
}
});
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
moveForward(distance = this.moveSpeed) {
if (!this.enabled) return;
const direction = this.camera.direction;
const horizontalDirection = new Cesium.Cartesian3(
direction.x, direction.y, 0
);
Cesium.Cartesian3.normalize(horizontalDirection, horizontalDirection);
const movement = Cesium.Cartesian3.multiplyByScalar(
horizontalDirection, distance, new Cesium.Cartesian3()
);
Cesium.Cartesian3.add(this.camera.position, movement, this.camera.position);
}
moveBackward(distance = this.moveSpeed) {
this.moveForward(-distance);
}
moveLeft(distance = this.moveSpeed) {
if (!this.enabled) return;
const right = this.camera.right;
const horizontalRight = new Cesium.Cartesian3(right.x, right.y, 0);
Cesium.Cartesian3.normalize(horizontalRight, horizontalRight);
const movement = Cesium.Cartesian3.multiplyByScalar(
horizontalRight, -distance, new Cesium.Cartesian3()
);
Cesium.Cartesian3.add(this.camera.position, movement, this.camera.position);
}
moveRight(distance = this.moveSpeed) {
this.moveLeft(-distance);
}
}
// 使用
const fpController = new FirstPersonController(viewer);
fpController.enable();
5.6.3 跟踪实体
// 基本跟踪
const entity = viewer.entities.add({
name: '移动目标',
position: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 100),
model: {
uri: 'model.glb',
scale: 1.0
}
});
// 设置跟踪
viewer.trackedEntity = entity;
// 带偏移的跟踪
viewer.trackedEntity = entity;
viewer.camera.setView({
destination: viewer.camera.position,
orientation: new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(45), // 从侧后方观察
Cesium.Math.toRadians(-20), // 略微向下
500 // 距离 500 米
)
});
// 自定义跟踪逻辑
function customTracking(viewer, entity, offset) {
viewer.scene.postRender.addEventListener(function() {
const position = entity.position.getValue(viewer.clock.currentTime);
if (position) {
viewer.camera.lookAt(position, offset);
}
});
}
customTracking(viewer, entity, new Cesium.HeadingPitchRange(0, -0.5, 1000));
// 停止跟踪
viewer.trackedEntity = undefined;
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
5.7 相机工具类
// 完整的相机工具类
class CameraUtils {
constructor(viewer) {
this.viewer = viewer;
this.camera = viewer.camera;
}
// 获取当前视角状态
getState() {
const cartographic = this.camera.positionCartographic;
return {
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
height: cartographic.height,
heading: Cesium.Math.toDegrees(this.camera.heading),
pitch: Cesium.Math.toDegrees(this.camera.pitch),
roll: Cesium.Math.toDegrees(this.camera.roll)
};
}
// 恢复视角状态
setState(state) {
this.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(
state.longitude, state.latitude, state.height
),
orientation: {
heading: Cesium.Math.toRadians(state.heading),
pitch: Cesium.Math.toRadians(state.pitch),
roll: Cesium.Math.toRadians(state.roll)
}
});
}
// 飞行到坐标
flyTo(lon, lat, height, options = {}) {
return new Promise(resolve => {
this.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, height),
orientation: {
heading: Cesium.Math.toRadians(options.heading || 0),
pitch: Cesium.Math.toRadians(options.pitch || -45),
roll: 0
},
duration: options.duration || 2,
complete: resolve
});
});
}
// 获取视野范围
getViewExtent() {
const rectangle = this.camera.computeViewRectangle();
if (rectangle) {
return {
west: Cesium.Math.toDegrees(rectangle.west),
south: Cesium.Math.toDegrees(rectangle.south),
east: Cesium.Math.toDegrees(rectangle.east),
north: Cesium.Math.toDegrees(rectangle.north)
};
}
return null;
}
// 获取中心点
getCenter() {
const canvas = this.viewer.scene.canvas;
const centerScreen = new Cesium.Cartesian2(
canvas.clientWidth / 2,
canvas.clientHeight / 2
);
const ray = this.camera.getPickRay(centerScreen);
const center = this.viewer.scene.globe.pick(ray, this.viewer.scene);
if (center) {
const cartographic = Cesium.Cartographic.fromCartesian(center);
return {
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude)
};
}
return null;
}
// 截图
screenshot(filename = 'screenshot.png') {
const canvas = this.viewer.scene.canvas;
const link = document.createElement('a');
link.download = filename;
link.href = canvas.toDataURL('image/png');
link.click();
}
}
// 使用
const cameraUtils = new CameraUtils(viewer);
// 保存和恢复视角
const state = cameraUtils.getState();
console.log('当前视角:', state);
// 稍后恢复
cameraUtils.setState(state);
// 飞行到指定位置
await cameraUtils.flyTo(116.4, 39.9, 15000, {
heading: 45,
pitch: -30,
duration: 3
});
5.8 本章小结
本章详细介绍了 CesiumJS 的相机系统:
- 相机基础:坐标系、属性说明
- 定位方法:setView、flyTo、lookAt
- 移动旋转:平移、缩放、旋转操作
- 相机动画:缓动函数、自定义动画
- 相机事件:事件监听、状态监控
- 高级功能:视角限制、第一人称、实体跟踪
- 工具类:完整的相机操作封装
在下一章中,我们将详细介绍 Entity API 实体管理。
5.9 思考与练习
- 实现一个相机漫游路径编辑器。
- 创建平滑的相机过渡动画效果。
- 实现地图双击放大功能。
- 开发第一人称漫游模式的完整实现。
- 实现视角书签功能(保存和恢复多个视角)。

浙公网安备 33010602011771号