原文链接及内容

效果如下视频所示:

示例代码如下:

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
<style>
@import url(../templates/bucket.css);

td {
font-size: 9px;
}

.note {
color: #ffff00;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay">
<h1>加载中...</h1>
</div>
<div id="toolbar">
<table class="infoPanel">
<tbody>
<tr>
<td>
单击加载Cesium地球的主窗口,然后使用键盘更改设置。<br />
<span class="note">注:单击窗口的作用是使焦点聚焦在窗口中的画布上。</span>
</td>
</tr>
<tr>
<td style="border-bottom: 2px solid white"></td>
</tr>
<tr>
<td>航向角度: <span id="heading"></span>°</td>
</tr>
<tr>
<td>← 向左转/→ 向右转</td>
</tr>
<tr>
<td>俯仰角度: <span id="pitch"></span>°</td>
</tr>
<tr>
<td>↑ 向上仰/↓ 向下俯</td>
</tr>
<tr>
<td>翻滚角度: <span id="roll"></span>°</td>
</tr>
<tr>
<td>Shift + ← 向左翻滚/Shift + → 向左翻滚</td>
</tr>
</tbody>
</table>
</div>
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
* Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result)方法:
* 从提供的原点为中心、轴由给定的俯仰-滚转-航向角计算出的参考框架,计算一个 4x4 变换矩阵。
* 它可以将一个局部坐标系(由原点和方向定义)转换到到提供的椭球体的固定参考框架(第3个参数)。
* 这个变换矩阵通常用于表示物体在地球上的位置和姿态,例如飞行器、车辆或传感器的定位。
*/

const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
skyBox: false,
shouldAnimate: true,
baseLayerPicker: false,
});
viewer.cesiumWidget.creditContainer.style.display = "none";

const canvas = viewer.canvas;
canvas.setAttribute("tabindex", "0"); //需要将焦点聚焦至画布上
canvas.addEventListener("click", function () {
canvas.focus();
});
canvas.focus();

const scene = viewer.scene;
const camera = viewer.camera;
const controller = scene.screenSpaceCameraController;
let r = 0;

const hpRoll = new Cesium.HeadingPitchRoll();
const hpRange = new Cesium.HeadingPitchRange();
const deltaRadians = Cesium.Math.toRadians(1.0);

//以下定义的本地坐标系皆为右手坐标系
const localFrames = [
{
pos: Cesium.Cartesian3.fromDegrees(-123.075, 44.045, 4000.0),
// x: 东, y: 北, z: 上
converter: Cesium.Transforms.eastNorthUpToFixedFrame,
comments: "典型的东北上(East North Up)本地坐标系",
},
{
pos: Cesium.Cartesian3.fromDegrees(-123.075, 44.05, 4500.0),
// x: 北, y: 西, z: 上
converter: Cesium.Transforms.localFrameToFixedFrameGenerator(
"north",
"west"
),
comments: "北西上(North West Up)本地坐标系",
},
{
pos: Cesium.Cartesian3.fromDegrees(-123.075, 44.04, 3500.0),
// x: 南, y: 上, z: 西
converter: Cesium.Transforms.localFrameToFixedFrameGenerator("south", "up"),
comments: "南上西(South Up West)本地坐标系",
},
{
pos: Cesium.Cartesian3.fromDegrees(-123.075, 44.05, 3500.0),
// x: 上, y: 东, z: 北
converter: Cesium.Transforms.localFrameToFixedFrameGenerator("up", "east"),
comments: "上东北(Up East North)本地坐标系",
},
{
pos: Cesium.Cartesian3.fromDegrees(-123.075, 44.04, 4500.0),
// x: 下, y: 东, z: 南
converter: Cesium.Transforms.localFrameToFixedFrameGenerator(
"down",
"east"
),
comments: "下东南(Down East South)本地坐标系",
},
];

const primitives = [];
const hprRollZero = new Cesium.HeadingPitchRoll();

for (let i = 0; i < localFrames.length; i++) {
const position = localFrames[i].pos;
const converter = localFrames[i].converter;
const comments = localFrames[i].comments;
try {
const planePrimitive = scene.primitives.add(
await Cesium.Model.fromGltfAsync({
url: "../SampleData/models/CesiumAir/Cesium_Air.glb",
modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame(
position,
hpRoll,
Cesium.Ellipsoid.WGS84,
converter
),
minimumPixelSize: 128,
})
);

primitives.push({
primitive: planePrimitive,
converter: converter,
position: position,
});
} catch (error) {
console.log(`加载模型出错: ${error}`);
}
const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(
position,
hprRollZero,
Cesium.Ellipsoid.WGS84,
converter
);
scene.primitives.add(
new Cesium.DebugModelMatrixPrimitive({
modelMatrix: modelMatrix,
length: 300.0,
width: 10.0,
})
);

const positionLabel = position.clone();
positionLabel.z = position.z + 300.0;
viewer.entities.add({
position: positionLabel,
label: {
text: comments,
font: "12px Helvetica",
fillColor: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
HorizontalOrigin: Cesium.HorizontalOrigin.RIGHT,
},
});
}

primitives[0].primitive.readyEvent.addEventListener((model) => {
// 缩放至模型
r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
controller.minimumZoomDistance = r * 0.5;
const center = model.boundingSphere.center;
const heading = Cesium.Math.toRadians(90.0);
const pitch = Cesium.Math.toRadians(0.0);
hpRange.heading = heading;
hpRange.pitch = pitch;
hpRange.range = r * 100.0;
camera.lookAt(center, hpRange);
});

document.addEventListener("keydown", function (e) {
switch (e.code) {
case "ArrowDown":
// 向下俯
hpRoll.pitch -= deltaRadians;
if (hpRoll.pitch < -Cesium.Math.TWO_PI) {
hpRoll.pitch += Cesium.Math.TWO_PI;
}
break;
case "ArrowUp":
// 向上仰
hpRoll.pitch += deltaRadians;
if (hpRoll.pitch > Cesium.Math.TWO_PI) {
hpRoll.pitch -= Cesium.Math.TWO_PI;
}
break;
case "ArrowRight":
if (e.shiftKey) {
// 向右滚动
hpRoll.roll += deltaRadians;
if (hpRoll.roll > Cesium.Math.TWO_PI) {
hpRoll.roll -= Cesium.Math.TWO_PI;
}
} else {
// 向右转
hpRoll.heading += deltaRadians;
if (hpRoll.heading > Cesium.Math.TWO_PI) {
hpRoll.heading -= Cesium.Math.TWO_PI;
}
}
break;
case "ArrowLeft":
if (e.shiftKey) {
// 向左翻滚
hpRoll.roll -= deltaRadians;
if (hpRoll.roll < 0.0) {
hpRoll.roll += Cesium.Math.TWO_PI;
}
} else {
// 向左转
hpRoll.heading -= deltaRadians;
if (hpRoll.heading < 0.0) {
hpRoll.heading += Cesium.Math.TWO_PI;
}
}
break;
default:
}
});

const headingSpan = document.getElementById("heading");
const pitchSpan = document.getElementById("pitch");
const rollSpan = document.getElementById("roll");

viewer.scene.preUpdate.addEventListener(function (scene, time) {
for (let i = 0; i < primitives.length; i++) {
const primitive = primitives[i].primitive;
const converter = primitives[i].converter;
const position = primitives[i].position;
Cesium.Transforms.headingPitchRollToFixedFrame(
position,
hpRoll,
Cesium.Ellipsoid.WGS84,
converter,
primitive.modelMatrix
);
}
});

viewer.scene.preRender.addEventListener(function (scene, time) {
headingSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.heading).toFixed(1);
pitchSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.pitch).toFixed(1);
rollSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.roll).toFixed(1);
});