在开发 Cesium 三维应用时,相机控制是用户交互体验的核心部分。有时我们需要限制用户的视角范围,比如在展示特定区域时防止用户迷失方向,或者在虚拟导览中保持合理的观察视角。本文将详细介绍如何在 Cesium 中实现相机约束,限制用户可以浏览的角度、高度和区域范围。
在三维 GIS 应用中,相机约束有多种实用场景:
- 导览体验优化:在虚拟导览中,限制视角可以提供更有引导性的体验
- 防止无效视角:避免用户旋转到奇怪的角度或者深入地下
- 聚焦关注区域:保持用户视线在特定区域内,突出重点
- 减少用户迷失:在大型场景中,限制可浏览范围防止用户迷失
- 场景性能优化:限制相机可以减少渲染和数据加载的范围,提升性能
相机约束的类型
Cesium 的相机约束主要包括以下几种:
1. 俯仰角约束(Pitch)
限制相机的上下俯仰角度,防止用户看到地下或转到不适合的角度。
2. 航向角约束(Heading)
限制相机的水平旋转角度,控制用户可以环视的范围。
3. 缩放距离约束(Distance)
限制相机与目标点的距离,控制用户可以放大和缩小的程度。
4. 高度约束(Height)
限制相机距离地表的高度,防止用户飞得过高或过低。
5. 地理范围约束(Geographic Bounds)
限制相机在特定地理区域内移动,确保用户不会飞离关注区域。
实现方案
下面是一个完整的实现方案,通过扩展 Cesium 的 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
|
Camera.prototype.setCameraVision = function (cameraVisionOptions) { const scene = this._coreMap.scene; const camera = this._camera;
if (cameraVisionOptions.limitDistance) { scene.screenSpaceCameraController.minimumZoomDistance = cameraVisionOptions.limitDistance[0]; scene.screenSpaceCameraController.maximumZoomDistance = cameraVisionOptions.limitDistance[1]; } this.preRenderListener = () => { const cameraPosition = camera.positionCartographic;
const currentHeading = Cesium.Math.toDegrees(camera.heading); const currentPitch = Cesium.Math.toDegrees(camera.pitch);
let needUpdate = false; let newHeading = currentHeading; let newPitch = currentPitch; let newHeight = cameraPosition.height;
if (cameraVisionOptions.limitHeading) { if ( currentHeading < cameraVisionOptions.limitHeading[0] || currentHeading > cameraVisionOptions.limitHeading[1] ) { needUpdate = true; newHeading = Cesium.Math.clamp( currentHeading, cameraVisionOptions.limitHeading[0], cameraVisionOptions.limitHeading[1] ); } }
if (cameraVisionOptions.limitPitch) { if ( currentPitch < cameraVisionOptions.limitPitch[0] || currentPitch > cameraVisionOptions.limitPitch[1] ) { needUpdate = true; newPitch = Cesium.Math.clamp( currentPitch, cameraVisionOptions.limitPitch[0], cameraVisionOptions.limitPitch[1] ); } } if (cameraVisionOptions.limitHeight) { if ( cameraPosition.height < cameraVisionOptions.limitHeight[0] || cameraPosition.height > cameraVisionOptions.limitHeight[1] ) { needUpdate = true; newHeight = Cesium.Math.clamp( cameraPosition.height, cameraVisionOptions.limitHeight[0], cameraVisionOptions.limitHeight[1] ); } } if (needUpdate) { camera.setView({ destination: Cesium.Cartesian3.fromRadians( cameraPosition.longitude, cameraPosition.latitude, newHeight ), orientation: { heading: Cesium.Math.toRadians(newHeading), pitch: Cesium.Math.toRadians(newPitch), roll: camera.roll, }, }); }
if (cameraVisionOptions.center && cameraVisionOptions.radius) { const centerCartesian = Cesium.Cartesian3.fromDegrees( cameraVisionOptions.center[0], cameraVisionOptions.center[1] ); const currentPosition = camera.position; const distance = Cesium.Cartesian3.distance(currentPosition, centerCartesian);
if (distance > cameraVisionOptions.radius) { const direction = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(currentPosition, centerCartesian, new Cesium.Cartesian3()), new Cesium.Cartesian3() ); const newPosition = Cesium.Cartesian3.multiplyByScalar( direction, cameraVisionOptions.radius, new Cesium.Cartesian3() ); Cesium.Cartesian3.add(centerCartesian, newPosition, newPosition); camera.position = newPosition; } } }; scene.preRender.addEventListener(this.preRenderListener); };
Camera.prototype.removeCameraVision = function () { const scene = this._coreMap.scene;
scene.screenSpaceCameraController.minimumZoomDistance = 1.0; scene.screenSpaceCameraController.maximumZoomDistance = Number.POSITIVE_INFINITY;
if (this.preRenderListener) { this._coreMap.scene.preRender.removeEventListener(this.preRenderListener); this.preRenderListener = null; } };
|
代码解析
setCameraVision 方法
这个方法用于设置相机约束,接受一个包含多种限制参数的配置对象:
- 距离限制设置:
1 2 3 4
| if (cameraVisionOptions.limitDistance) { scene.screenSpaceCameraController.minimumZoomDistance = cameraVisionOptions.limitDistance[0]; scene.screenSpaceCameraController.maximumZoomDistance = cameraVisionOptions.limitDistance[1]; }
|
这里直接使用 Cesium 内置的缩放距离限制属性,非常直观。
- preRender 事件监听:
1 2 3 4
| this.preRenderListener = () => { }; scene.preRender.addEventListener(this.preRenderListener);
|
设置一个在每帧渲染前执行的监听器,这是实现动态约束的关键。
- 俯仰角和航向角约束:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| if (cameraVisionOptions.limitHeading) { if ( currentHeading < cameraVisionOptions.limitHeading[0] || currentHeading > cameraVisionOptions.limitHeading[1] ) { needUpdate = true; newHeading = Cesium.Math.clamp( currentHeading, cameraVisionOptions.limitHeading[0], cameraVisionOptions.limitHeading[1] ); } }
if (cameraVisionOptions.limitPitch) { }
|
检查当前角度是否超出限制,如果是则计算新的合法角度。
- 高度约束:
1 2 3
| if (cameraVisionOptions.limitHeight) { }
|
限制相机的高度范围。
- 应用新视图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if (needUpdate) { camera.setView({ destination: Cesium.Cartesian3.fromRadians( cameraPosition.longitude, cameraPosition.latitude, newHeight ), orientation: { heading: Cesium.Math.toRadians(newHeading), pitch: Cesium.Math.toRadians(newPitch), roll: camera.roll, }, }); }
|
如果需要更新,则设置新的相机视图。
- 地理范围约束:
1 2 3 4
| if (cameraVisionOptions.center && cameraVisionOptions.radius) { }
|
这是一个圆形地理围栏,限制相机不能离开指定的圆形区域。
removeCameraVision 方法
这个方法用于移除相机约束:
1 2 3 4 5 6 7 8 9 10 11
| Camera.prototype.removeCameraVision = function () { const scene = this._coreMap.scene;
scene.screenSpaceCameraController.minimumZoomDistance = 1.0; scene.screenSpaceCameraController.maximumZoomDistance = Number.POSITIVE_INFINITY;
if (this.preRenderListener) { this._coreMap.scene.preRender.removeEventListener(this.preRenderListener); this.preRenderListener = null; } };
|
这里重置了缩放距离限制,并移除了 preRender 事件监听器,完全释放相机约束。
使用方法
下面是几个使用示例:
示例 1:限制俯仰角和航向角
1 2 3 4 5 6 7 8
| const pitchOnlyOptions = { limitPitch: [-30, 30], limitHeading: [0, 180], };
viewer.camera.setCameraVision(pitchOnlyOptions);
|
示例 2:设置完整约束
1 2 3 4 5 6 7 8 9 10 11 12
| const fullConstraints = { limitPitch: [-45, 0], limitHeading: [-180, 180], limitDistance: [1000, 10000], limitHeight: [500, 5000], center: [116.3912, 39.9073], radius: 50000, };
viewer.camera.setCameraVision(fullConstraints);
|
示例 3:创建虚拟导览
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const tourConstraints = { limitPitch: [-10, 45], limitHeight: [1.7, 100], limitDistance: [0, 1000], center: [120.1551, 30.2741], radius: 3000, };
viewer.camera.setCameraVision(tourConstraints);
viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(120.1551, 30.2741, 30), orientation: { heading: Cesium.Math.toRadians(45), pitch: Cesium.Math.toRadians(0), roll: 0, }, });
|
示例 4:移除约束
1 2
| viewer.camera.removeCameraVision();
|