前言
Cesium 作为一个功能强大的 WebGL 三维地球仪库,内置了丰富的三维地图展示能力。在 Cesium 中,我们可以通过 Entity(实体)在三维场景中添加和控制各种三维对象,如点、线、面、模型等。本文将介绍 Cesium 中实体的增删改查操作。
概述
添加到场景中的实体都保存在 viewer.entities
中。我们在控制台中输出 viewer.entities
,在其原型对象上寻找对应的处理方法,下面是操作代码:
const token = import.meta.env.VITE_CESIUM_TOKEN; Cesium.Ion.defaultAccessToken = token; const viewer = new Cesium.Viewer("cesiumContainer", { //图层控件显隐控制 timeline: false, //隐藏时间轴 animation: false, //隐藏动画控制器 geocoder: false, //隐藏地名查找控制器 homeButton: false, //隐藏Home按钮 sceneModePicker: false, //隐藏投影方式控制器 baseLayerPicker: false, //隐藏图层选择控制器 navigationHelpButton: false, //隐藏帮助按钮 fullscreenButton: false, //隐藏全屏按钮 }); global.$viewer = viewer; //创建一个立方体 const box = new Cesium.Entity({ id:"box", position: Cesium.Cartesian3.fromDegrees(114.3, 39.9, 20000), box: { dimensions: new Cesium.Cartesian3(40000, 30000, 10000), //盒子的长宽高 material: Cesium.Color.RED, //盒子颜色 outline: true, //边框 outlineColor: Cesium.Color.WHITE, //边框颜色 }, }); //将立方体添加到场景中 const boxEntity = viewer.Entities.Add (box); //视角飞行至立方体 viewer.camera.flyTo( { destination: Cesium.Cartesian3.fromDegrees(114.3, 39.9, 40000), // 目的地的经纬度坐标 duration: 4, } // 动画持续时间,默认为3秒 ); //输出viewer.entities console.log(viewer.entities); });
可以看到有以下entity属性和方法,我将根据这些方法进行entity操作:
我们可以总结出如下思维导图:
一、实体的添加
在Cesium中,我们可以通过两种方式添加实体。
1 .直接添加
const viewer = new Cesium.Viewer("cesiumContainer", { //相关配置代码... }); viewer.entities.add({ id: "point", position: Cesium.Cartesian3.fromDegrees(116.3, 39.9), point: { pixelSize: 10,//点像素大小 color: Cesium.Color.RED,//点颜色,不能用rgb等css方法,需要用Cesium.Color outlineColor: Cesium.Color.WHITE, outlineWidth: 2, }, });
2 .先创建后添加
const viewer = new Cesium.Viewer("cesiumContainer", { //相关配置代码... }); const point = new Cesium.Entity({ position: Cesium.Cartesian3.fromDegrees(116.3, 39.9), point: { pixelSize: 10,//点像素大小 color: Cesium.Color.RED,//点颜色,不能用rgb等css方法,需要用Cesium.Color outlineColor: Cesium.Color.WHITE, outlineWidth: 2, }, }); viewer.entities.add(point);
这里我们没有使用了 new 关键字对 Cesium 的 Entity 类进行实例化;但是有细心的读者可能会发现,即使不用 new Cesium. Entity (), 只定义一个对象,实体 box 照样可以加入到 viewer 空间中,这是因为我们使用了下面这行代码:
viewer.entities.add({...})
在 Cesium 中,viewer.entities.add()
方法可以接受一个 Entity 实例或一个对象字面量作为参数。如果传递的是一个对象字面量,Cesium 会自动将其转换为一个 Entity 实例。因此,即使你没有显式地使用 new Cesium.Entity()
来创建一个 Entity 实例,也可以正常运行。
建议当创建代码和添加代码分开时采用类创建,一体时采用字面量创建,以便更加清楚的表明自己的意图,使代码更容易阅读和维护。
二、实体的删除:
关于删除实体的方法有 remove,removeAll, removeById; 我将分别进行介绍:
1 . 删除特定实体
我们在上面添加实体的代码中定义了对应的实体变量 boxEntity,这里我们通过以下代码删除:
viewer.entities.remove(boxEntity);//如果已删除则返回true,如果该集合中不存在该实体,则返回false
2 . 删除所有点
viewer.entities.removeAll();//如果已删除则返回true,如果该集合为空,则返回false
3 . 根据 id 删除点
viewer.entities.removeById("box");//如果已删除则返回true,如果该集合中不存在该实体,则返回false
4 . 先获取实体,再删除
const box = viewer.entites.getById("box"); viewer.entities.remove(box);
5 . 自定义删除
因为 viewer. Entities 是按照输入的实体对象进行删除的,我们可以创建一个数组存储实体对象,然后根据数组方法遍历实体对象对其进行删除;代码如下:
let list1 = []; let list2 = []; const box1 = new Cesium.Entity({ id:"box", position: Cesium.Cartesian3.fromDegrees(114.3, 39.9, 20000), box: { dimensions: new Cesium.Cartesian3(40000, 30000, 10000), //盒子的长宽高 material: Cesium.Color.RED, //盒子颜色 outline: true, //边框 outlineColor: Cesium.Color.WHITE, //边框颜色 }, }); viewer.entities.add(box1); list1.push(box1); const box2 = new Cesium.Entity({ id:"box", position: Cesium.Cartesian3.fromDegrees(114.3, 39.9, 20000), box: { dimensions: new Cesium.Cartesian3(40000, 30000, 10000), //盒子的长宽高 material: Cesium.Color.RED, //盒子颜色 outline: true, //边框 outlineColor: Cesium.Color.WHITE, //边框颜色 }, }); list2.push(box2); viewer.entities.add(box2); //要删除某个类别,只需要遍历对应的数组进行删除即可 list1.forEach((item)={ viewer.entities.remove(item); }) //也可以在数组循环中设定条件针对删除; list2.forEach((item)={ if(item.id=="box"){ viewer.entities.remove(item); } })
三、实体的修改
我们可以通过CallbackProperty
回调属性实现实体的动态修改;使用代码如下:
//创建一个呼吸小球 const ellipsoid = new Cesium.Entity({ position: Cesium.Cartesian3.fromDegrees(114.3, 39.9), ellipsoid: { //radii是radius的复数,因为这个创建的是椭球体,所以要设定三个轴的长度一致,即可构成球体; radii: new Cesium.CallbackProperty(function () { //呼吸小球 var radius = 10000 + Math.sin(Cesium.JulianDate.now().secondsOfDay) * 3000;//根据sin 函数和 时间变量实现动态半径变化; return new Cesium.Cartesian3(radius, radius, radius); }, false), heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,//实体底部贴地 material: Cesium.Color.RED.withAlpha(0.5), }, }); //将立方体添加到场景中 const boxEntity = viewer.entities.add(ellipsoid); //视角飞行至立方体 viewer.camera.flyTo( { destination: Cesium.Cartesian3.fromDegrees(114.3, 39.9, 40000), // 目的地的经纬度坐标 duration: 4, } // 动画持续时间,默认为3秒 );
- CallbackProperty,可以看作是一个特殊的属性类型,参数是被封装好的回调函数;会持续执行。
- 当开发者使用
new Cesium.CallbackProperty(callback)
创建一个回调属性并将其应用于场景中的某个元素(例如立方体)时,Cesium 会在渲染过程中不断获取这个回调属性的值。每次 Cesium 需要获取这个回调属性的值时,它都会调用这个回调函数,并使用返回的值作为属性值。- 这个过程并不需要你显式地编写循环。相反,它是由 Cesium 内部的渲染引擎自动触发的。当 Cesium 渲染场景时,它会不断更新场景中的各个元素,并在需要时获取回调属性的值。
- 因此,即使你没有显式地编写循环,Cesium 也会在渲染过程中多次调用回调函数来计算属性值。它并不是只计算一次,而是每次 Cesium 需要获取这个属性的值时都会调用回调函数来计算属性值。因此,开发者可以使用
Cesium.CallbackProperty
来实现各种动态效果。
四、实体的查询
Cesium 中,我们可以通过以下方法查询实体:
1.通过 getById
根据 id 查询
const box = viewer.entites.getById("box");
2.通过 getOrCreateEntity
根据 id 查询
如果该 id 对应的实体不存在,则将其添加到集合中。
const entity = viewer.entites.getOrCreateEntity (id);
3.通过 contains
判断实体是否存在
const isExist = viewer.entities.contains (entity)//如果实体存在,则返回true,否则返回false;
五、性能优化
在批量添加或修改实体时,可以使用viewer.entities.suspendEvents()
和viewer.entities.resumeEvents()
方法来提高性能。这两个方法分别用于暂停和恢复 Cesium 实体集合的事件处理。
开发者可以在批量更新之前调用 viewer.entities.suspendEvents()
来暂停事件处理,然后在更新完成后调用 viewer.entities.resumeEvents()
来恢复事件处理。
案例代码如下:
// 暂停事件处理 viewer.entities.suspendEvents(); // 执行批量更新 for (let i = 0; i < 1000; i++) { viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(Math.random() * 360 - 180, Math.random() * 180 - 90), point: { pixelSize: 10, color: Cesium.Color.RED, }, }); } // 恢复事件处理 viewer.entities.resumeEvents();
- 当开发者调用
viewer.entities.suspendEvents()
时,Cesium 会暂停实体集合的事件处理。这意味着在暂停期间,Cesium 不会触发与实体相关的事件,例如definitionChanged
事件。这可以提高批量添加或更新实体时的性能。 - 在暂停事件处理期间,用户仍然可以继续向实体集合中添加、删除或更新实体。这些操作不会受到暂停事件处理的影响。但是,在暂停事件处理期间,Cesium 不会触发与实体相关的事件。这意味着如果您在暂停期间对实体进行了更改,那么这些更改不会立即反映在场景中。
- 当用户调用
viewer.entities.resumeEvents()
时,Cesium 会恢复实体集合的事件处理。这意味着 Cesium 会重新开始触发与实体相关的事件。如果您在暂停期间对实体进行了更改,那么在恢复事件处理后,Cesium 会触发相应的事件来反映这些更改。
Tips
viewer.entities.suspendEvents()
和viewer.entities.resumeEvents()
这两个方法只能用于暂停和恢复 Cesium 实体集合的事件处理。它们只能用于控制与实体相关的事件,不能用于控制场景中其他元素的事件。
六、计算时间间隔
1 .应用场景
computeAvailability()
方法的一个具体应用场景是在使用 CZML 数据源时。CZML 是一种用于描述时间动态图形的 JSON 格式。它允许你定义随时间变化的实体,例如卫星轨迹或飞机飞行路径。
2. 使用细节
当开发者使用 CZML 数据源加载 CZML 数据时,Cesium 会根据 CZML 数据创建实体集合。每个实体都可以具有可用性时间间隔,表示实体在何时可见或有效。这个时候可以使用 computeAvailability()
方法来计算整个实体集合的可用性时间间隔。计算得到的可用性时间间隔表示实体集合在给定时间范围内的可用性。这个信息可以帮助你更好地控制场景中的元素。例如,你可以使用这个信息来确定何时显示或隐藏与实体集合相关的其他元素,或者调整场景的时间轴以查看实体集合。
3 . 代码案例:
var viewer = new Cesium.Viewer("cesiumContainer"); var scene = viewer.scene; // 加载 CZML 数据 var czml = [ { id: "document", name: "CZML Path", version: "1.0", }, { id: "path", name: "path with GPS flight data", availability: "2022-08-04T16:00:00Z/2023-08-04T16:02:00Z", position: { epoch: "2022-08-04T16:00:00Z", cartographicDegrees: [ 0, -117.0, 35.0, 100000, 30, -117.0, 36.0, 100000, 60, -117.0, 37.0, 100000, 90, -117.0, 38.0, 100000, 120, -117.0, 39.0, 100000, ], }, path: { material: { solidColor: { color: { rgba: [255, 255, 0, 255], }, }, }, width: 5, leadTime: 10, trailTime: 1000, resolution: 5, }, }, ]; var dataSource = Cesium.CzmlDataSource.load(czml); viewer.dataSources.add(dataSource).then(function (dataSource) { var entityCollection = dataSource.entities; // 计算实体集合的可用性时间间隔 var availability = entityCollection.computeAvailability(); // 创建标签实体 var labelEntity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(-117.0, 35.0, 100000), label: { text: "Satellite in view", show: false, }, }); // 监听场景的 preRender 事件 scene.preRender.addEventListener(function () { var currentTime = Cesium.JulianDate.fromDate(new Date()); // 判断当前时间是否在可用性时间间隔内 if ( Cesium.JulianDate.greaterThanOrEquals( currentTime, availability.start ) && Cesium.JulianDate.lessThanOrEquals(currentTime, availability.stop) ) { // console.log(availability.start, availability.stop, currentTime); // 如果在可用性时间间隔内,则显示标签实体 labelEntity.label.show = true; } else { // 如果不在可用性时间间隔内,则隐藏标签实体 labelEntity.label.show = false; } }); });
- 这段代码加载了一个包含路径数据的 CZML 文件,并使用
Cesium.CzmlDataSource.load
方法将其转换为数据源。然后,它调用viewer.dataSources.add
方法将数据 - 添加到场景中。当数据源加载完成后,它使用
computeAvailability()
方法来计算实体集合的可用性时间间隔。 - 接着,这段代码创建了一个标签实体,并将其添加到场景中。标签实体的初始状态为隐藏。
- 然后,这段代码使用
scene.preRender.addEventListener
方法来监听场景的渲染事件。在事件处理函数中,它获取当前时间,并判断当前时间是否在可用性时间间隔内。如果当前时间在可用性时间间隔内,则显示标签实体;否则,隐藏标签实体。
当前是 2023 年,结束时间是 2022 年,标签不可见,将结束时间改为 2023 年后,标签可见;
CZML数据解释:
每四个元素表示一个数据点,第一个元素是相对于
epoch
的时间偏移量,后三个元素分别表示经度、纬度和高度。第一个数据点的时间偏移量为
0
,表示它的时间值为2012-08-04T16:00:00Z + 0 = 2012-08-04T16:00:00Z
。第二个数据点的时间偏移量为30
,表示它的时间值为2012-08-04T16:00:00Z + 30 = 2012-08-04T16:00:30Z
。
"2012-08-04T16:00:00Z/2012-08-04T16:02:00Z"
意味着实体只在2012-08-04T16:00:00Z
到2012-08-04T16:02:00Z
之间对观察者可见。在这个时间范围之外,实体不会显示在场景中。
总结
综上,本文系统介绍了 Cesium 开发中实体各种增删改查的操作方法,内容全面且重点突出,可以作为 Cesium 实体操作的指南,希望对 Cesium 开发者有所裨益。
项目地址:
如果觉得我的文章对您有帮助,三连+关注便是对我创作的最大鼓励!或者一个star🌟也可以😂.