Cesium案例解析(三)——Camera相机
1. 概述
Cesium的Camera案例,展示了其关于漫游器镜头的控制,能够调整视图的位置。这里改进了一下这个实例,使之能够展示一些自己关注的兴趣点的情况,并总结遇到的问题。
2. 实例
2.1. Camera.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta name="description" content="Fly to a specified location or view a geographic rectangle.">
<meta name="cesium-sandcastle-labels" content="Beginner, Tutorials, Showcases">
<title>Cesium Demo</title>
<script type="text/javascript" src="../Build/Cesium/Cesium.js"></script>
<style>
@import url(../Build/Cesium/Widgets/widgets.css);
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
font-family: sans-serif;
background: #000;
}
.fullSize {
display: block;
position: absolute;
top: 0;
left: 0;
border: none;
width: 100%;
height: 100%;
}
#toolbar {
margin: 5px;
padding: 2px 5px;
position: absolute;
}
</style>
</head>
<body>
<div id="cesiumContainer" class="fullSize"></div>
<div id="toolbar">
<select id = "camera_select", class="cesium-button">
<option value="undefined">
相机选项
</option>
<option value="undefined">
飞行至某一点——武汉大学
</option>
<option value="undefined">
飞行至某区域——武汉市
</option>
<option value="undefined">
设置相机点——华中科技大学
</option>
<option value="undefined">
设置相机区域——上海市
</option>
<option value="undefined">
从武大飞向华科
</option>
</select>
</div>
<script src="Camera.js"></script>
</body>
</html>
这段代码在数字地球展示组件的基础上新添加了一个视图控制的下拉列表框,选择相应的选项能够将当前的视图调整到对应的位置。
2.2. Camera.js
//Add your ion access token from cesium.com/ion/
Cesium.Ion.defaultAccessToken = '你在Cesium申请的key';
var tdtKey = "你在天地图申请的key";
'use strict';
//默认BING影像地图
var viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: Cesium.createWorldImagery({
style: Cesium.IonWorldImageryStyle.AERIAL
}),
baseLayerPicker: false
});
//全球影像中文注记服务
var imageryLayers = viewer.scene.imageryLayers;
var tdtAnnoLayer = imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
url: "http://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}&tk=" + tdtKey,
layer: "tdtAnnoLayer",
style: "default",
format: "image/jpeg",
tileMatrixSetID: "GoogleMapsCompatible"
}));
var camera_select = document.getElementById("camera_select");
if (camera_select) {
camera_select.onchange = function gradeChange() {
switch (camera_select.selectedIndex) {
case 1:
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
}
});
break;
case 2:
viewer.camera.flyTo({
destination: Cesium.Rectangle.fromDegrees(113.683333, 29.966667, 115.083333, 31.366667)
});
break;
case 3:
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
}
});
break;
case 4:
viewer.camera.setView({
destination: Cesium.Rectangle.fromDegrees(120.86667, 30.66667, 122.2, 31.883333)
});
break;
case 5: {
var whdxOptions = {
destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
duration: 5,
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
}
};
var hzkjdxOptions = {
destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
},
duration: 5,
//flyOverLongitude: Cesium.Math.toRadians(60.0)
};
whdxOptions.complete = function () {
setTimeout(function () {
viewer.camera.flyTo(hzkjdxOptions);
}, 1000);
};
// if (adjustPitch) {
// tokyoOptions.pitchAdjustHeight = 1000;
// laOptions.pitchAdjustHeight = 1000;
// }
viewer.camera.flyTo(whdxOptions);
}
break;
default:
break;
}
}
}
这段代码首先添加了Cesium.Viewer默认的Bing影像地图和天地图的中文标注;然后根据id获取HTML页面的下拉列表框控件camera_select;最后根据选项调整相应的相机视图。这里展示了几种调整视图的方式。
2.2.1. 飞行至某一点
设置相机镜头逐渐从当前位置飞行到某一点是通过Cesium.Camera的flyTo()函数实现的,其具体的函数定义如下:
该函数传入了键值对配置对象,其中destination、orientation这两项,分别表示相机镜头的位置和姿态。本例中相应的代码如下:
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
}
});
这段设置相机视图的代码意思就是,将相机的位置移动到经纬度位置(114.35231209, 30.53542614),离地面5000米的点;航向角(heading)设置为0度,俯仰角(pitch)设置为-90度,滚转角(roll)设置为0度。实际页面的显示效果为逐渐飞往某一点:
此时数字地球会显示在武汉大学附近,视线看上去会垂直与地面,并且东西南北方向也基本上与常规地图一致。实际上当不设置姿态参数orientation只设置位置参数destination也能达到同样的效果,说明(0.0,-90.0,0.0)的三个姿态角是设置相机视图的默认值。在Cesium的设定中,heading、pitch、roll的定义如下:
这说明航向角(heading)是绕Z后负方向旋转的角度,俯仰角(pitch)是绕Y轴负方向旋转的角度,滚转角(roll)是绕X轴正方向旋转的角度。那么问题来了,这个定义里面的X、Y、Z轴的指的是什么呢?我这里认为这个函数蕴含了一种视图变换,使得基于相机的视空间坐标系成为一种类似于一种北东地站心坐标系(NED)坐标系,XYZ轴指的正是这个视空间坐标系的XYZ轴。在这个视空间坐标系中,Z轴垂直球面向下(Down),Y轴沿纬线指东(East),X轴沿经线向北(North),而位于视空间坐标系原点的相机的姿态为由南看向北。在这种情况下,只需要使相机绕Y轴正向旋转90度,也就是俯仰角(pitch)设为90,就可以得到视线垂直于地图,东西南北向正常的视图。
2.2.2. 飞行至某区域
flyTo()函数另外一个很有用的功能就是根据设定的范围显示视图,这在显示特定空间的视图时特别有用,例如加载的三维模型的范围,一个地区的范围等等。实现也很很简单,只需要给位置参数destination传入一个Cesium.Rectangle对象即可:
viewer.camera.flyTo({
destination: Cesium.Rectangle.fromDegrees(113.683333, 29.966667, 115.083333, 31.366667)
});
将武汉市的经纬度范围传入,实际的显示结果如下:
2.2.3. 两地之间飞行
flyTo()函数还可以传入一个配置项complete,可以给其设定一个飞行结束后再运行的函数,通过这个配置项可以实现两地或多地飞行:
var whdxOptions = {
destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
duration: 5,
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
}
};
var hzkjdxOptions = {
destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
},
duration: 5,
//flyOverLongitude: Cesium.Math.toRadians(60.0)
};
whdxOptions.complete = function () {
setTimeout(function () {
viewer.camera.flyTo(hzkjdxOptions);
}, 1000);
};
// if (adjustPitch) {
// tokyoOptions.pitchAdjustHeight = 1000;
// laOptions.pitchAdjustHeight = 1000;
// }
viewer.camera.flyTo(whdxOptions);
这段代码分别定义了两个飞行配置项whdxOptions和hzkjdxOptions,并且给whdxOptions的complete项配置了一个函数,表示完成1S之后,自动进行hzkjdxOptions的飞行。运行结果如下图所示:
2.2.4. 设置视图到某一点
设置当前视图通过setView()函数实现的,它跟flyTo()最大的不同是没有持续时间,没有飞行过程,是立即生效的。其具体的配置选项也比较相似,都是需要设置位置以及姿态:
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: Cesium.Math.toRadians(0.0)
}
});
2.2.5. 设置视图到某区域
设置具体的显示范围,也是立即生效,这两个部分因为与flyTo()函数比较类似,就不再具体讲解了。
viewer.camera.setView({
destination: Cesium.Rectangle.fromDegrees(120.86667, 30.66667, 122.2, 31.883333)
});
3. 其他
3.1. 事件及相应函数
Cesium.Camera还提供了当前视图发生变化的事件changed、视图发生移动的事件moveStart/moveEnd,它们都可以通过addEventListener()给其添加相应的响应函数。
3.2. setReferenceFrame
自带案例Camera中还提供了另外一种视图控制方式:
function setReferenceFrame() {
Sandcastle.declare(setReferenceFrame);
var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
// View in east-north-up frame
var camera = viewer.camera;
camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
camera.lookAtTransform(transform, new Cesium.Cartesian3(-120000.0, -120000.0, 120000.0));
// Show reference frame. Not required.
referenceFramePrimitive = scene.primitives.add(new Cesium.DebugModelMatrixPrimitive({
modelMatrix: transform,
length: 100000.0
}));
}
这段代码的意思是选定一个经纬度的点,可以计算出以该点为中心的东北天(ENU)站心坐标系与地心坐标系的转换矩阵,将这个矩阵传入给Cesium.Camera的lookAtTransform函数,从而达到设置视图的目的。但是这样做会导致当前世界坐标系发生变化,当前漫游器的键鼠交互操作不再以地心坐标系原点为中心,而以站心坐标系的原点为中心,导致这个时候的键鼠交互操作难以操作。
3.3. viewInICRF
Cesium默认是基于ITRF,也就是国际地球地心参考框架。自带案例还提供了一种将其转换为ICRF参考框架的视图设置方式。关于ICRF我也不是很了解,查阅网上资料只知道是一种原点在太阳系的质心的天文参考框架,留待以后需要用到的时候再研究。
4. 参考
[1]. 北东地/东北天两种导航坐标系与姿态转换
[2]. Cesium中的相机—HeadingPitchRoll
[3]. Cesium类HeadingPitchRoll及heading、pitch、roll等参数详解