基于Cesium原生方法在项目上的一些使用

  • Post author:
  • Post category:其他


刚来公司的项目上的时候,是从离职的前同事手中接过了代码。看别人的代码永远是一种很难受的行为,开立一个新篇章,存储那些关于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在被创建的时候就知道自己被点击的时候该去执行怎样的代码逻辑。从而减少在被点击的时候去根据属性判断该做什么。一个主动一个被动,各有优劣吧。



版权声明:本文为GhostPaints原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。