1. 前言
三维模型可以提供更真实的视觉效果,cesium将三维模型与其他地理数据(如地形、影像和矢量数据)结合,提供综合的可视化平台,用户能够直观理解地理信息和对象的空间关系。八青妹将通过cesium/Clamp to 3D Model这个例子,循序渐进,讲解下是如何实现的。
我们使用的模型可以在github中下载: github.com/.../GroundV…。下载后将文件放到项目的public目录下。
2.加载三维模型
方法一
很多的教程上是用以下方式举例如何加载模型的:
var model = Cesium.Model.fromGltf({
url : '/GroundVehicle.glb',
modelMatrix : modelMatrix,
scale : 200.0
});
scene.primitives.add(model);
这种写法已经废弃了。现在的写法是用异步的方式Cesium.Model.fromGltfAsync()进行调用,上述的案例需要改为:
const model =await Cesium.Model.fromGltfAsync({
url : '/GroundVehicle.glb',
modelMatrix : modelMatrix,
scale : 200.0
});
scene.primitives.add(model);
方法二
或者将模型添加到实体当中。
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromRadians(longitude, latitude),
model: {
uri: "/GroundVehicle.glb",
},
});
添加到实体之后,需要自动调整相机的位置和方向。使用viewer.trackedEntity = entity,相机便会自动调整视角,将焦点保持在该实体上。
const viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false,
});
const scene = viewer.scene;
const longitude = -2.1480545852753163;
const latitude = 0.7688240036937101;
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromRadians(longitude, latitude),
model: {
uri: "/GroundVehicle.glb",
},
});
viewer.trackedEntity = entity;
效果如下所示,是一个静态的车。
这里有一点需要注意的地方,在Viewer的属性中,要允许动画效果的运行。即
const viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false,
shouldAnimate: true//启用动画
});
启用动画后,模型中的车轮开始原地转动了。下面将在方法二的基础上讲解。
3. 创建动态的实体
在八青妹cesium(七)和cesium(八)系列中讲解了一些简单的交互,主要是通过变更实体的label或者动态给已有实体增加属性。这里我们将尝试下创建动态的实体。
3.1 画一个点
从最最简易的说起,简易到只是画一个点😄。点的位置在原来实体放置的经纬度基础上增加一个高度。设置下点的大小和颜色。
// 创建一个动态颜色的实体
const point = viewer.entities.add({
position: Cesium.Cartesian3.fromRadians(longitude, latitude, 4),
point: {
pixelSize: 10,
color: Cesium.Color.YELLOW,
},
});
看下效果:
3.2 动态更改点的颜色
在这之前,了解下new Cesium.CallbackProperty()是cesium中用于创建动态属性的构造函数,该函数允许定义一个回调函数,该函数在每次场景更新时被调用,从而返回当前的属性值。
Cesium.JulianDate.now()函数用于获取当前时间,返回一个表示当前时刻的JulianDate对象,JulianDate 是 Cesium 中用来处理时间的类,在涉及动画、时间序列数据或需要时间计算的场景中常用。将当前时间在一天中的秒数取模,得到当前秒数在周期 duration 内的相对位置,再除以 duration,可以将结果标准化到 0 到 1 的范围。用代码来解释会更清楚:
const duration=60;//定义一个周期
const time = Cesium.JulianDate.now();
const offset=(time.secondsOfDay % duration) / duration;//对当前时间取模和归一化
这个偏移量的计算很常见,划个重点。
根据色调、饱和度和亮度创建颜色实例是Cesium.Color.fromHsl(hue,saturation,lightness, alpha, result),其中入参hue, saturation, lightness, alpha的值的范围均为0~1,将上述offset作为色调hue入参。
综合看下这段的代码:
const duration = 60;
// 创建一个动态颜色的实体
const point = viewer.entities.add({
position: Cesium.Cartesian3.fromRadians(longitude, latitude, 4),
point: {
pixelSize: 10,
color: new Cesium.CallbackProperty(() => {
const time = Cesium.JulianDate.now();
const cycleTime = (time.secondsOfDay % duration) / duration;
return Cesium.Color.fromHsl((time.secondsOfDay % duration) / duration, 1.0, 0.5);
}, false),
},
});
刷新页面后,会看到原点开始不停地在平缓的改变颜色。如果想让颜色改变的快一些,就缩短动画周期,将duration的值减小。
3.3 动态展示实体各个部分的高度
我们加载的模型是一辆车,现在要实现的功能是:从车后方到车前测量车中间位置的高度。
首先,要使用 sampleHeight 方法获取高度,这个方法需要的参数为笛卡尔坐标的经纬度。
由于sampleHeight 方法依赖于 Cesium 加载的地形数据。如果场景中没有加载地形数据,可能无法获取高度。这里先加个延迟3秒获取,举例:
const cartographic = new Cesium.Cartographic(longitude, latitude);
setTimeout(() => {
if (scene.sampleHeightSupported) {
const height = scene.sampleHeight(cartographic);
console.log(height);//结果是:2.5083911982150893
}
}, 3000);
这个测量的高度,也将包括后面添加实体的高度,所以需要将后面添加的实体放到objectsToExclude。
//排除点的高度
const objectsToExclude = [point];
const height = scene.sampleHeight(cartographic,objectsToExclude);
将上述代码片段都结合起来,编写函数updatePosition(),返回位置信息,添加到动态的实体当中。
现在,再来阅读下面的代码,应该会比较轻松了吧☺️。
// 动画的范围
const range = 0.000001;
// 周期的时间
const duration = 8;
const point = viewer.entities.add({
position: new Cesium.CallbackProperty(updatePosition, false),
point: {
pixelSize: 10, // 点的大小
color: Cesium.Color.YELLOW, // 点的颜色
disableDepthTestDistance: Number.POSITIVE_INFINITY, // 禁用深度测试
},
label: {
show: false,
showBackground: true,
font: "14px monospace",
horizontalOrigin: Cesium.HorizontalOrigin.LEFT, // 水平对齐方式
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 垂直对齐方式
pixelOffset: new Cesium.Cartesian2(5, 5), // 标签的偏移量
disableDepthTestDistance: Number.POSITIVE_INFINITY, // 禁用深度测试
},
});
// 排除点的高度
const objectsToExclude = [point];
// 笛卡尔坐标
const cartographic = new Cesium.Cartographic();
// 动画的更新函数
function updatePosition(time, result) {
// 偏移量
const offset = (time.secondsOfDay % duration) / duration;
// 计算动画的经度和纬度
cartographic.longitude = longitude - range + offset * range * 2.0;
cartographic.latitude = latitude;
// 计算动画的高度
let height;
if (scene.sampleHeightSupported) {
height = scene.sampleHeight(cartographic, objectsToExclude);
}
if (Cesium.defined(height)) {
cartographic.height = height;
point.label.text = `${Math.abs(height).toFixed(2).toString()} m`;
point.label.show = true;
} else {
cartographic.height = 0.0;
point.label.show = false;
}
// 计算动画的位置
return Cesium.Cartographic.toCartesian(cartographic, Cesium.Ellipsoid.WGS84, result);
}
如果想从车的右侧测量到车的左侧,而不是从后到前测量高度的话,可以尝试更改以下代码:
//偏移范围缩小
const range = 0.0000003;
//经度不变,更改纬度
cartographic.longitude = longitude;
cartographic.latitude = latitude - range + offset * range * 2.0;
总结
细心的你也许会发现,当将相机的视角增加高度,会发现这个点的大小和label标签并不会越来越小,它在画布中始终保持10px,因为动态增加的实体并不是添加到模型实体当中,是独立出来的。本节内容从加载模型的方式,到动态添加点,再到让这个点根据实体的高度进行位置变更。是个比较基础的案例。越是基础,越是重要。