刚来公司的项目上的时候,是从离职的前同事手中接过了代码。看别人的代码永远是一种很难受的行为,开立一个新篇章,存储那些关于Cesium的,在项目中“这样设计应该更好”的一些规范和逻辑。多加注释这种个人喜好的代码规范就不在这篇文章的讨论范围内了。
一、新建数据集datasource来管理entity
1.我们该如何存储Entity对象,让我们更方便的找到它
前同事,在项目最喜欢用数组了。每次新增的Entity都是用数组去存储,然后清空的时候,去遍历这个数组,再一个个的去除。
var entityArr = []; // 存储绘制的实体entity
function removeAllEntity(){
entityArr.forEach(item){
viewer.entities.remove(entity)
}
}
初学Cesium的时候,觉得这也算是个不错的方法。毕竟可以随时去除掉地图上我们想要去除的entity。后来,有一个需求,要求地图上的实体进行聚合的时候,我接触到了Cesium官方的数据集合:new Cesium.DataSource()
并且,在官方的示例中,使用了:new Cesium.CustomDataSource(name) 来完成一个聚合的效果。这给了我们什么启发呢?我们可以参照PS里面的图层思想,不需要把所有的entity都绘制在viewer.entities里面,而是绘制在我们自己定义的数据集里面。
var selfDataSource = new Cesium.CustomDataSource('moduleName-whatUse');
viewer.dataSources.add(selfDataSource);
selfDataSource.entities.add({...option实体配置项});
先创建一个datasource,然后将这个集合添加到viewer里面,最后我们就只需要向这个集合添加绘制我们的entity里面了。
这样做的好处在哪里?哪些场景可以应用到?
需要频繁的控制显示隐藏与删除某些entity的时候。比如说,地图上已经绘制好了全国身份的边界线,然后点击某个省份的时候,其他省份的边界线就不要了,只显示点击选中的省份。回退到全国视角的时候,又需要重新把它们绘制出来。
如果是用数组存储这些对象,可能就需要遍历这个数组,然后每一个entity设置为隐藏,或者删除。
但是如果这些entity都绘制在我们自己定义的datasource上,那我们就只需要控制这个datasource的显示与隐藏了。
var arr = []; // 存了很多entity对象
var datasource = new Cesium.CustomDataSource(name); // 自己定义的数据集合
viewer.dataSources.add(dataSource);
// 用数组去控制,删除/隐藏
arr.forEach(function (entity) {
viewer.entities.remove(entity); // 删除
entity.show = false; // 隐藏
})
// 数据集合来控制,删除/隐藏
datasource.show = false; // 隐藏
datasource.entities.removeAll(); // 删除所有
理论上,我们可以创建很多个数据集来管理我们的entity。
二、使用Cesium自带的属性,来描述一个entity
每一个绘制的entity都可以自己设置id和name,但是工作中,根据一个坐标点绘制的entity只用name和id来描述它,有点太鸡肋了。特别是涉及到点击事件的时候。
试想一下这样的一种需求情况:
某个地图上的图标被点击后,需要在UI界面上显示它的一些信息。
这个还好,我们用一个ID来标识它之后,用接口去请求数据,然后渲染更新界面UI。
那如果说,这个需求变动成这样呢?
某个地图上的图标被点击后,需要在UI界面上显示它所在的分类,上级分类,这个坐标点所在的等级。
也不难吧,不就多请求几次接口的问题?但是,我们通过接口请求到地图坐标的时候,我们就已经知道了这个坐标的一些信息。我们是不是要更加利用这些信息?
它就是:properties。Entity的配置项的其中一个,这个并不是一些可视化的图形什么的,更多的用来描述这个Entity。
let entity = viewer.entities.add({
position,
billboard,
properties: {
'name': Object对象
}
})
在讲述使用之前,我来说一下前同事如何处理 entity的点击事件。
添加地图点击事件监听 =》判断是否点击到了实体 =》拿到entity的name或者id,然后判断是否包含某个字符串 =》对应的点击事件。
前同事绘制entity的时候,要么是给这个entity取了个特殊点的ID,或者是name用一个下划线来拼接的字符串。很多东西都用下划线来拼接成字符串然后赋值给name?
前同事的点击事件逻辑:
var handle = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handle.setInputAction(function(event) {
var pick = viewer.scene.pick(event.position);
if (Cesium.defined(pick) && (pick.id) && (pick.id._name)){ // 这里还同时判断有没有给name
if(pick.id._name !== '某个字符串'){
// 处理逻辑
// 发起接口请求
}else if(pick.id._name.indexOf('某个字符串') > -1){
// 处理逻辑
// 发起接口请求
}
}
},Cesium.ScreenSpaceEventType.LEFT_CLICK)
如果我们在绘制的时候,用properties来存储,那我们只要这样。
let entity = viewer.entities.add({
position,
billboard,
properties: {
'name': Object对象,
'address': Object对象
}
})
handle.setInputAction(function(event) {
var pick = viewer.scene.pick(event.position);
if (Cesium.defined(pick) && (pick.id)){
var ent = pick.id;
if (ent.properties){ // 设置了properties属性
if (ent.properties.hasProperty('name')) { // 拥有name属性
const name= entity.properties.name.getValue();
const address= entity.properties.address.getValue();
// 执行逻辑
}
}
}
},Cesium.ScreenSpaceEventType.LEFT_CLICK)
好处在哪?很简单,你用下划线分割读取一个字符串,你还要知道第几个是什么意思。我直接拿到这个entity的properties,我就知道这个坐标点的其他信息了。
我们通过接口请求过来,循环读取数组来绘制的时候,我们就可以把每一个子项都分别存到对应entity里面。还可以自己控制存哪些。哪种方式优势更大,哪种方式后续的扩展性更好,一看就知道了。
三、给entity设置各自的点击事件
项目上有时候需要给地图上绘制的entity设置一些点击事件,大致设置方式都是差不多的。获取cesium的场景,然后设置各种监听事件。
viewhandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
viewhandler.setInputAction(function (event){
....点击事件逻辑......
},Cesium.ScreenSpaceEventType.LEFT_CLICK)
在点击的时候,获取点击到的entity实体,然后从实体中获取一些信息,然后执行代码。对于功能相同的entity,点击事件里面的代码还算比较少,不会显得很臃肿,毕竟都是取同样的变量名执行同样的逻辑。如果有三四类,或者十多种不同的执行逻辑呢?不断的使用if来判断或者switch来执行不同的业务逻辑?
如果我们给entity设置好一个点击事件了,在触发逻辑的时候只要去执行它。这样点击事件里面不就会少很多的代码?当然判断是否点击到了实体entity的逻辑还是必须的。
const entity = viewer.entities.add({
name: '自己定义的entity名称',
position: 坐标,
label: {
一些label的配置项
}
})
entity.click = function() {
console.log(this) // 这会打印entity实体类的信息
...其他代码逻辑
this.label.text = '点击之后更改的名称'
}
上面的代码,我们实现了一个点击事件,当调用entity.click()的时候,它会自己去改变这个entity里面的label文字。然后我们在注册的点击事件里面去判断。
viewhandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
viewhandler.setInputAction(function (event){
....点击事件逻辑......
var pick = viewer.scene.pick(click.endPosition);
if(pick && pick.id){
const entity = pick.id;
pick.click && pick.click();
}
},Cesium.ScreenSpaceEventType.LEFT_CLICK)
这样,我们就不必要专门在点击事件里面去判断这个entity到底去执行怎样的点击事件逻辑了。并且还有一个好处就是,可以获取创建时候的上下文。创建entity通常都是通过遍历一个数组来批量创建的,所以,我们可以在click事件里面存储创建entity时候的上下文。
let context = this;
for(let index=0;index < 10;index++){
const entity = viewer.entities.add({
name: '自己定义的entity名称',
position: 坐标,
label: {
一些label的配置项
}
})
entity.click = function() {
console.log(this) // 这会打印entity实体类的信息
console.log(index);
console.log(context);
...其他代码逻辑
this.label.text = '点击之后更改的名称'
return {
index,
context
}
}
}
这样,我们在执行entity的时候,顺便还能返回执行时候的上下文,还能知道index的值。虽然这两个值可以通过entity的property属性来存储就是了。。。
为entity设置点击事件,主要还是实现一种:让entity在被创建的时候就知道自己被点击的时候该去执行怎样的代码逻辑。从而减少在被点击的时候去根据属性判断该做什么。一个主动一个被动,各有优劣吧。