Cesium 日常问题整理

2021-09-22

在 VS Code 中实现Cesium的代码提示,参照链接:VSCode实现Cesium的自动提示功能_liveanddie的博客-CSDN博客

为了防止这片博文链接失效,特此记录。

在Cesium 的源码中,找到CesiumUnminified文件夹中的Cesium.d.ts文件,将其文件放到项目中去。然后在需要代码提示的文件开头,用以下代码引用:

/// 

如果使用npm命令安装失败的,可以直接从github上,将提示文件下载下来,或者复制文件中的所有内容,在本地新建一个同名的文件,将内容粘贴进去。然后像上面一样,直接引用这个提示文件,这样,代码自动提示的更多更全面。

github地址:DefinitelyTyped/index.d.ts at master · DefinitelyTyped/DefinitelyTyped · GitHub

在项目中,两个widget切换的时候,设置了一个相机camera的flyto方法的飞行动画。由于两个widget的视角不同,一个是360的俯视角的A界面,一个是357的倾斜视角的B界面。导致出现一个问题,在flyto方法相机还在飞的过程中,跳转到A界面时,相机视角依旧是B界面的视角。

解决办法:

由于A界面用setView方法对视角进行了调整,因此在调整之前,用官方的completeFlight  方法,完成当前的相机飞行并将相机立即移动到其最终目的地。如果没有进行任何飞行,则此功能不执行任何操作。

2021-09-15

创建地图上的entity时,同时设置了billboard和label,导致部分视角只显示billboard广告牌,而没有出现label标签文字。

出现此问题的原因为,用来做背景的billboard图片遮挡住了文字。

解决办法一:(相对有效)

为label的配置,设置显示背景,并且设置一个背景颜色。

此处由于billboard的image为一个黑色边框的纯白色图标,因此label的backgroundColor设置为了纯白色。可以依据情况,设置一个透明颜色的背景

                    billboard: {
                        image: img,
                        width: 19,
                        height: 19
                    },

                    label: { //文字标签
                        text: imgName,
                        font: '500 30px Helvetica',// 15pt monospace
                        scale: 0.45,
                        style: Cesium.LabelStyle.FILL,
                        fillColor: Cesium.Color.BLACK,
                        pixelOffset: new Cesium.Cartesian2(1, -0), //偏移量
                        showBackground: true,                       //显示label的背景
                        backgroundColor: Cesium.Color.WHITE         //设置label的背景颜色
                    },

解决办法二:

更改广告牌billboard的颜色,使其具有一个透明度,让文字可以看到

                    billboard: {
                        image: img,
                        width: 19,
                        height: 19,
                        color: new Cesium.Color(1.0,1.0,1.0,0.8)
                    },

                    label: { //文字标签
                        text: imgName,
                        font: '500 30px Helvetica',// 15pt monospace
                        scale: 0.45,
                        style: Cesium.LabelStyle.FILL,
                        fillColor: Cesium.Color.BLACK,
                        pixelOffset: new Cesium.Cartesian2(1, -0), //偏移量
                    },

2021/12/30

在项目上有这么一个问题,在天地图以及其他地图服务上,绘制的区域都是和底图符合的没有偏移。这些服务大多是从服务器上加载使用的,没有偏移很正常。但是,天地图使用的是网络服务,百度地图也是使用的网络服务,在天地图上是没有偏移的,但是在百度电子地图上是有严重偏移的。

项目中所使用的百度电子地图,使用的类型是:midnight。深色的电子地图。

项目已经是快要完成的阶段,如果因为一张客户不常使用的底图而去改每个模块的代码,有点得不偿失。测试的工作量也很庞大,只能通过框架源码下手了。由于使用的是火星科技平台的Mars3D框架,官方文档中有专门处理百度地图加载的,但试了没有用。于是从源码下手了。

在源码中,有一个“BaiduImageryProvider”底图加载,在里面使用到了一个方法“WebMercatorTilingScheme”。根据Cesium的官方文档,这个方法是用来确定图片如何在椭圆球体上展开的。

Cesium 日常问题整理_第1张图片

 主要是这两个参数:rectangleSouthwestInMeters,rectangleNortheastInMeters

在源码中,BaiduImageryProvider里面专门设置了两个变量width和height,用来控制平铺位置。

偏移相差多少只能手动去更改这两个数值,一直到调整差不多没有多少偏移为止。这也是一个很偏门的法子。有时间还得要多去研究这个官方方法。

2022年1月25日

项目界面进行了大改,负责了一个模块的制作。涉及到了一些地图entity的操作。大致的要求是,第一个层级是全国层级,可以看到全国的所有省份在地图上的边界线。第二个层级是省份层级,点击了某个省份后就可以看到这个省份内所有市区的边界线。

因为还存在着另一个要求,当时遇到了一个报错,因为解决了没有截图到,所以记录一下。

要求就是,点击了某一个省份后,其他省份要变成黑色透明无边界线。所以采用的方法是,创建两个customdatasource来分别存放所有的省份entity,全国所有省份内市区的entity。

然后创建了两个方法,分别操作控制这个customdatasource里面的entity的透明度和边界线以及文字的可见度。

当时想法很简单,我先把所有的省份都变成黑色透明无边界线,然后再单独去显示那个被点击的省份。然后报错了,报错了一个webgl渲染失败的报错,说找不到id。

后面才知道,所有的js都走完后,cesium会统一去渲染地图上的实体,并且是异步的。也就是说,虽然按顺序执行了第一个方法渲染全部和第二个方法单独渲染。实际上这两样会产生严重的问题,因为渲染全部很耗费时间,单独渲染可能更快完成,导致了两个渲染操作都要去锁定那个entity,导致有一个方法无法再获取到entity的id。当一个实体entity接受到了更新的状态,cesium就会像锁表一样,不让其他方法去操作,而其他的渲染操作找不到这个实体entity,它就直接报错说webgl渲染失败。

出现这个情况的条件:这些entity开始是不可见的,有一个方法操作全部这些entity,有一个方法操作其中一个单独的entity。

2022年2月26日

公司另外一个项目有几个需求要求这个礼拜完成,把一些经验记录下来。要求模型放置之后可以进行旋转和缩放。通过完成这个功能,让我有了一些新的理解。

放置模型之后,我使用了mars3d提供的divPoint弹窗,在地图上绘制了一个div界面,里面放了按钮和input[type="range"]类型的拖动条。这些都是H5和JS方面的交互,主要记录 Cesium的一些entity操作。

由于创建model是在entity.model中,在其中有一个scale属性,可以直接设置好缩放比列。

最好在创建entity的时候,就设置model的最小缩放和最大缩放。 

Cesium 日常问题整理_第2张图片

接下来就是处理旋转功能了。当时创建的时候,并没有为entity.model设置 orientation这个值。导致后面我想要赋值的时候,直接给我报错了。如果放置的模型后续涉及到旋转操作,这个属性就必须要设置。主要定义俯仰角,官方的示例是这个: 

​​​​​​Cesium Sandcastle

我使用的俯仰角和旋转体系都是官方提供的那个方法,在官方文档中还有很多。我使用的是

orientation: Cesium.Transforms.headingPitchRollQuaternion(
     Cesium.Cartesian3.fromDegrees(x, y, 0.0),
     new Cesium.HeadingPitchRoll(0, 0, 0)
)

它让模型面朝东。

然后使用下面的代码,让它旋转:

  let entity = viewer.entities.getById(entityId);
  const rotates = Cesium.Transforms.headingPitchRollQuaternion(
    Cesium.Cartesian3.clone(entity.position._value),
    new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(value), 0, 0)
  )
  entity.orientation.setValue(rotates);

通过id或者其他方式,获取到要操作的entity。然后,把原先的entity的实体的position值克隆一份用作参数。

如果你直接传entity的position作为参数会报错,原因大概是不能调用自身的属性作为参数来赋值给自身的其他属性。会矛盾冲突,cesium官方给了克隆方法,那就用呗。

最好的解决办法,应该不是重新赋值。而是通过一个四元数Quaternion来转换现有的值,可惜四元数我没去学,估计和矩阵转换类似的算法吧。

2月27日    自己写的旋转和缩放的功能遇到了BUG,记录一下解决的办法和问题原因。

bug描述:另一个同事之前写好了一个模型按住可以拖动的效果,拖动了之后模型就没办法通过我那个窗口来调整方向了。看了一下日志是报错了,报错原因是不能从undefind里面获取orientation。

最后找到了bug原因,同事的拖动逻辑直接改了entity的orientation值,让其变成了一个回调方法。

顺便记录一下,如何实现模型的点击拖动。不是我写的逻辑,是同事写的逻辑,在此记录一下。

    mapLeftClick_Down: function (event) {
        this.pointDraged = viewer.scene.pick(event.position);//选取当前的entity
        this.leftDownFlag = true;
        if (this.pointDraged) {
            viewer.scene.screenSpaceCameraController.enableRotate = false;//锁定相机
        }
    },
    mapLeftClick_Up: function (event) {
        this.leftDownFlag = false;
        this.pointDraged = null;
        viewer.scene.screenSpaceCameraController.enableRotate = true;//解锁相机
    },
    mapMouseMove: function (event) {
        if (this.leftDownFlag === true && this.pointDraged != null) {
            var ray = viewer.camera.getPickRay(event.endPosition);
            var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
            this.pointDraged.id.position = new Cesium.CallbackProperty(function () {
                return cartesian;
            }, false);//此处根据具体entity来处理,也可能是pointDraged.id.position=cartesian;
        }
    },

主要是给cesium的地图绑定左键点下事件,左键松开事件,和鼠标移动事件。

        // 左键点击事件
this.handler.setInputAction(this.mapLeftClick_Down, Cesium.ScreenSpaceEventType.LEFT_DOWN);
this.handler.setInputAction(this.mapLeftClick_Up, Cesium.ScreenSpaceEventType.LEFT_UP);
        // 鼠标移动事件
this.handler.setInputAction(this.mapMouseMove, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

加了两个状态判断。主要逻辑是这样的:

左键点下:用pointDraged记录获取鼠标点击选中的目标。用leftDownFlag记录左键已经被摁下。如果确实选中了实体,则锁定相机。

鼠标移动:同时判断两种状态,如果两种状态都存在。则可以更改实体的position值。

左键松开:将两个状态重置,然后将相机解锁。

主要的逻辑在鼠标移动事件里面:

因为鼠标在地图上的移动,也只能获得一个x和y值,而位置在地球上需要一个三维的笛卡尔坐标值。所以需要用到这个方法。获取射线,然后再找到这个射线和地球的焦点。

Cesium 日常问题整理_第3张图片

 Cesium 日常问题整理_第4张图片

同事并没有直接给position赋值,而是使用了一个callback方法,每次获取这个值的时候就去执行里面的方法。我的那套逻辑没有考虑到这个问题,所以通过 entity.position._value 是没办法获得的。

改进之后:

  let entity = viewer.entities.getById(entityId);
  let rotates;
  if (entity.position._value) {
    rotates = Cesium.Transforms.headingPitchRollQuaternion(
      Cesium.Cartesian3.clone(entity.position._value),
      new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(value), 0, 0)
    )
  }else {
    rotates = Cesium.Transforms.headingPitchRollQuaternion(
      Cesium.Cartesian3.clone(entity.position.getValue()),
      new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(value), 0, 0)
    )
  }
  entity.orientation.setValue(rotates);

其实就是加了一个判断,回过神来才发现,entity.position.getValue() 不是一样可以获取值,直接用entity.position._value 反而不好。代码不想优化了,就这样吧。

2022/04/02  项目中遇到了新的需求,要在右下角实现一个小地图的功能,主要是嫌弃框架的鹰眼图只显示大区域,显示不到具体的街道和社区。

在实现这个功能的时候,参考了原框架自带的鹰眼图,发现人家是引用的另外一个写好的库。一个专门实现2d平面地图的js。名称叫做 leaflet.js。官网不需要工具,这点还是挺好的。官方网址:http://leafletjs.com

要实现的功能比较复杂,小地图的视角是固定的,俯视显示某一个区域。这个小地图里面,有一个矩形框,显示大地图的视角。

功能思路了比较简单,我也是根据鹰眼图来修改的。

1.使用leaflet.js生成一个平面2d地图,设置底图,并取消显示自带的缩放按钮。

2.固定视角

3.给大地图设置监听

第一步,可以参考官网的方法。我是这样写的:

// L是那个js文件定义好的全局变量,直接用就行
map = L.map('html里面设置的div的id', {
            center: [经度, 纬度],
            zoom: 12,    // 缩放等级
            zoomControl: false,    // 缩放控件不显示
            attributionControl: false    // 属性控件不显示
        });
// 设置2d地图服务
L.tileLayer('2d地图的服务地址', {
            }).addTo(this.map);

缩放等级要根据自己需要去调整。center就是2d地图加载完成后的视角。总得来说,leaflet有的功能cesium都有,毕竟人家是针对手机端做的2d地图。

然后就是后面两步了

map;
showStyle: {
        color: "#0000ff",
        weight: 1,
        fill: !0,
        stroke: !0,
        opacity: 1
},
litterMapRectangle = null;
sceneRenderHandler = function(e) {
        var litterMap_ext = this.getExtent(this.viewer),
            litterMap_i = L.latLng(litterMap_ext.ymin, litterMap_ext.xmin),
            litterMap_s = L.latLng(litterMap_ext.ymax, litterMap_ext.xmax),
            bounds = L.latLngBounds(litterMap_i, litterMap_s);
        this.map.setView([22.543563, 114.049363],12);
        this.litterMapRectangle ? this.litterMapRectangle.setBounds(bounds) :this.litterMapRectangle = L.rectangle(bounds, this.showStyle).addTo(this.map)
    },
getExtent= function(viewer) {
        var rectangle = viewer.camera.computeViewRectangle(),
            result = this.getMinMax(rectangle);
        if (result.xmax < result.xmin) {
            var s = result.xmax;
            result.xmax = result.xmin,
                result.xmin = s
        }
        if (result.ymax < result.ymin) {
            var s = result.ymax;
            result.ymax = result.ymin,
                result.ymin = s
        }
        return result
    },
getMinMax= function(rectangle) {
        var t = Number(Cesium.Math.toDegrees(rectangle.west)).toFixed(6),
            i = Number(Cesium.Math.toDegrees(rectangle.east)).toFixed(6),
            n = Number(Cesium.Math.toDegrees(rectangle.north)).toFixed(6);
        return {
            xmin: t,
            xmax: i,
            ymin: Number(Cesium.Math.toDegrees(rectangle.south)).toFixed(6),
            ymax: n
        }
    },

getExtent方法,是获取大地图的camera相机视角的矩阵,viewer.camera.computeViewRectangle()直接返回一个对象,里面存储了东西南北四个方向,知道了四个方向,自然就围成了一个矩形。然后使用getMinMax转换这四个方向,把它们换算。

调用两次L.latLng,得到两个坐标点。然后再调用L.latLngBounds传入这个两个坐标点,绘制出矩形。为什么不是四个点呢,因为知道了矩形斜对面的那个点了。

自定义的showStyle变量,就是设置这个在小地图上的矩形是啥颜色,多粗,透明什么的。如果需求说要根据视角大小改变这个矩形的颜色,那就每次都刷新矩形的样式。

知道如何刷新绘制2d小地图上的矩形了,接下来就是绑定大地图上的监听事件了。

这里建议这样绑定:

viewer.scene.postRender.addEventListener(

            自定义的方法, 方法的上下文);

这个是Cesium自带的方法,每次渲染地图的时候都会触发这个事件。频率大概是1秒10次。

为什么不用camera的change监听呢?一来,比较卡顿,刷新的不够快。二来呢,因为设置过,所以知道很蛋疼。

蛋疼的原因个人猜测是这样的,cesium的相机视角拖动后,即使你松开鼠标了,它相机视角也会平滑的滑行。估计滑行的时候不会触发change事件吧。

我这里使用的自定义方法是:sceneRenderHandler。所以这样给大地图绑定:

// 绑定
this.viewer.scene.postRender.addEventListener(this.sceneRenderHandler, this);



// 解绑
this.viewer.scene.postRender.removeEventListener(this.sceneRenderHandler, this);

leaflet把它看作一个小型的cesium,只能说用的时候还是要多去官网看文档。无用的知识增加了...

2022-04-13  项目上有一个路线漫游的功能,需要有连线。这个飞行对象在飞行的时候,有一根线连着某一个固定的点。左侧显示这些点的详情列表,还要动态变化距离。 

两个点形成的连线,一个点是固定的,一个点是动态变化的。主要使用的技术是cesium原生的new Cesium.CallbackProperty (callback, isConstant)。

Cesium 日常问题整理_第5张图片

 这是个回调方法,每次要获取值的时候,就调用这个方法去更新。哪些地方能用呢,这个看entity里面的每一个graphic就知道了。拿线段polyline来举例。

Cesium 日常问题整理_第6张图片

只要官方文档说,这个值可以是一个Property或者衍生的MaterialProperty ,用我的理解就是,你官方文档显示的值类型名字里面带了property这个单词,就都可以用这个方法。

viewer.entities.add({
    polyline: {
        positions: new Cesium.CallbackProperty(function (time, result) {
            let cartographic = 某个动态变化点的Cartographic值;
            // 将这个值转换为笛卡尔坐标
            const lon = Cesium.Math.toDegrees(cartographic.longitude);
            const lat = Cesium.Math.toDegrees(cartographic.latitude);
            // 重新赋值
            return Cesium.Cartesian3.fromDegreesArrayHeights(
                                    [item.longitude, item.latitude, self.pointHeight, lon, lat, cartographic.height],
                                    Cesium.Ellipsoid.WGS84,
                                    result
                                )
        }, false),
        width: 5,
        material: self.arrPolylineColor[index%self.arrPolylineColor.length]
    }
)

 上面的代码,是一个比较简单的使用CallbackProperty方法。Cesium官方的使用案例是下面这个链接,官方案例用到了time这个变化的值,固定增长的。

Cesium Sandcastlehttps://sandcastle.cesium.com/?src=Callback%20Property.html一个完整的动态变化线,并且显示线长度,然后左侧还更新列表里面的DOM数据。大概代码:

let arr = 一个数组,里面每一个对象都包含了左侧列表需要渲染的数据。
// 假设这个arr里面的每一个子项都是长这样的对象
{
    distance: 距离,
    name: 名字
}
// 假设列表的容器jquery是:$('#rongqi')
左侧列表大概布局是这样
div
    div 名字 /div
    div 距离 /div
/div

// 第一步,遍历这个数组
arr.forEach((item,index) => {
    // 首先,创建需要加入左侧列表的jquery对象
    const box = $('
'); // 加入名称 box.append($('
'+item.name+'
')); // 加入距离,这里设置一个变量,方便后续的callbackProperty里面可以直接更改。就不用单独设置一个id了。 const distance = $('
'+item.distance+'
'); // 将这个盒子加入左侧列表 $('#rongqi').append(box); // 然后创建 entity const entity = viewer.entities.add({ // 由于label 使用的位置是整个entity的位置,所以如果动态线需要显示label,这个entity也要设置为动态变化的 position: new Cesium.CallbackProperty(回调方法,false), polyline: { // 点位置是动态的,所以也是动态的 positions: 回调方法 // 线材质,线颜色,线宽度等等.... }, label: { // 文本值是变化的,所以也是动态的 text: new Cesium.CallbackProperty(function (){ const juli = 某种方法求得的距离。 distance.html(juli); return '一个字符串,让label显示。' },false) } }) })

基本实现逻辑就是这样。需求里面有一个范围限制,超过某一个固定距离,这条线就要隐藏掉。这里我遇到一个很重要的知识点,也让我头疼了一天。

// 代码逻辑还是上面一样,不一样的是单独把entity拿出来设置。

forEach(
    // 设置好了div
    // 用一个变量存储设置的entity
    const entity = viewer.entities.add({});
    // 将entity的position单独设置
    entity.position = new Cesium.CallbackProperty(function() {
        .........之前的代码逻辑.......
        const juli = 某个方法获得的距离
        if(超过了某个设定的值){
            entity.polyline.show = false;
            entity.label.show = false;
        }else{
            entity.polyline.show = true;
            entity.label.show = true;
        }
        .........之前的代码逻辑.......
        return .......
    },false)
)

好了,这样写就会遇到一个bug了。这个bug就是,当一个entity里面的graphic,比如说上面代码设置的polyline,label 里面的show设置为false的时候。cesium就不会去执行callbalProperty方法了。准确的说,当一个entity里面所有的graphic的show都不可见的时候,这个entity里面所有的回调方法都不会去执行。亲测

可能是cesium出于性能方面的考虑,你一个polyline都看不见了,就没必要每毫秒都去执行回调方法来更新你polyline的属性了。

但是需求里面说,左侧列表的距离的值还是要变化啊。不能因为polyline和label看不到了就不更新它。于是,我们就需要创建一个billboard,不需要设置其他值。只需要:

billboard: { show: true }

这样,这个entity一直有一个graphic显示。它的position的回调方法就一直会执行。

根据我的测试结果,如果polyline的show是false,那么为polyline设置的动态回调方法也不会去执行。

2022-5-06   另一个项目上使用到了路线规划,地图上取两点,然后调用接口去获取路径信息。

不仅是国内,也要支持国际。国际得话就使用mapbox,国内的就使用高德和百度的路径规划。

功能实现也不是很难,按步骤解决:

1.实现地图取点,地图点击取点和输入经纬度取点

2.请求接口,判断国际路线还是国内路线

3.将接口返回的路径数据显示在地图上

第一个思路也比较简单:

let startPoint = false;        // 起点取点状态
let endPoint = false;          // 终点取点状态

// 设置两个点击按钮,分别改变这个状态。这两个状态是互斥的,一个启用另一个就要关闭
$('起点按钮').click(()=>{
    startPoint=true;
    endPoint=false;
})


在cesium的地图点击事件里面分别进行判断
.....
    if(startPoint){
        拿到点击的坐标点
        在地图上绘制一个显示的东西
        重新将startPoint设置为false
        return
    }else if(endPoint){
        拿到点击的坐标点
        在地图上绘制一个显示的东西
        重新将endPoint设置为false
        return
    }
.....

// 如果功能要求支持多路径,意味着有多个起点和多个终点。那就设置一个变量,记录当前是第几条路线
1.建立两个数组,分别存储起点entity和终点entity
这样的话,重复取起点的时候就可以清除上一个了
currentLineIndex = 0;
startArr = [];   endArr = [];

// 点击事件逻辑更改一下,加判断
if(startPoint){
        拿到点击的坐标点
        在地图上绘制一个显示的东西entity
        // 判断上一个entity是否有,有的话就移除,并重新设置
        if(startArr[currentLine]){
            viewer.entities.remove(startArr[currentLine]);
        }
        startArr[currentLine] = entity;
        重新将startPoint设置为false
        return
    }else if(endPoint){
        拿到点击的坐标点
        在地图上绘制一个显示的东西
// 判断上一个entity是否有,有的话就移除,并重新设置
        if(endtArr[currentLine]){
            viewer.entities.remove(endtArr[currentLine]);
        }
        endtArr[currentLine] = entity;
        重新将endPoint设置为false
        return
    }

起点和终点显示的entity分别管理虽然麻烦,但易于分别处理,毕竟可以执行输入经纬度取点,就意味着起点终点并不是一样都有。

路线绘制看个人,高德和百度返回的路径规划需要转换,用数组的map方法转换一下再绘制比较好。也用数组来存储绘制好的路线。

这样,有一个公共的变量存储当前的路线,只要取下标就能拿到想要的路线。只有在接口请求成功之后,再改变currentLineIndex的值。

问题来了,路线上需要有一辆车动起来。功能就像场景漫游一样,Cesium官方也有自己的案例:

Cesium Sandcastlehttps://sandcastle.cesium.com/?src=Interpolation.html大概的设计思路是差不多的:

1.时间轴设置开始时间,设置结束时间。

2.entity的position设置

3.entity的label文字的改变。

// 假设接口返回的path路径数据为一个数组,数组每一个成员为一个经纬度数组  [经度,纬度]
pathArr = [........]

var property = new Cesium.SampledPositionProperty();
let startTime = new Cesium.JulianDate.fromDate(new Date());    // 当前时间为开始时间
let endiTime = null;
// 初始坐标,用于监听变化
let lastLon = pathArr[0][0];
let lastLat = pathArr[0][1];
// 总距离
let lastDis = 0;

// 花费固定的时间,假设是60秒
const timeStep = 60 / pathArr.length;

循环遍历
for(let i=0;i

因为timeClockTickCallBackFun方法会持有textChange变量,而entity的label也是动态获取的,获取的值也是textChange变量。当textChange被改变的时候,entity上label显示的文字也会改变。

如果是多条路线,那时间轴的起点startTime就使用固定值,不需要每次创建的时候取当前时间为开始时间了。

5月19日  

遇到了一个非常奇怪的bug,从一个json文件里面获取了所有公司的位置,里面也有properties存储了这些公司的一些信息描述。然后呢,通过下拉框过滤条件,在地图上显示这些点位。

bug报错是一个渲染报错,“Range Error: Invalid array length”。最后发现这个问题出现在label的text文字上。报这个错误的原因暂时不清楚,只能记录一下。

登录页面需要做一个地球,用夜色底图,加上json文件里面的线条,流动效果。

参考的效果是按github首页上面的地球来做,还要求自转。

cesium地球自转的代码如下:

class GlobeRotate {
    constructor(viewer, options) {
        this._viewer = viewer;
        this._options = options;
    }

    // 根据国际天体参考系计算旋转矩阵
    _icrf() {
        if (this._viewer.scene.mode !== Cesium.SceneMode.SCENE3D) {
            return ture;
        }
        let icrfToFixed = Cesium.Transforms.computeIcrfToFixedMatrix(this._viewer.clock.currentTime);
        if (icrfToFixed) {
            let camera = this._viewer.camera;
            let offset = Cesium.Cartesian3.clone(camera.position);
            let transform = Cesium.Matrix4.fromRotationTranslation(icrfToFixed);
            // 偏移相机,否则会场景旋转而地球不转
            camera.lookAtTransform(transform, offset);
        }
    }

    // 绑定事件
    _bindEvent() {
        // 转动的速度设置
        this._viewer.clock.multiplier = this._options.time * 1000;
        // 初始化为单位矩阵
        this._viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
        this._viewer.scene.preRender.addEventListener(this._icrf, this);
    }

    // 解除绑定
    _unbindEvent() {
        this._viewer.clock.multiplier = 1;
        this._viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
        this._viewer.scene.preRender.removeEventListener(this._icrf, this);
    }

    // 开始旋转
    start() {
        this._viewer.clock.shouldAnimate = true;
        this._unbindEvent();
        this._bindEvent();
        return this;
    }

    // 停止旋转
    stop() {
        this._unbindEvent();
        return this;
    }
}


let globeRotate = new GlobeRotate(viewer,{time: 5});
globeRotate.start();

很早以前在某个网站上找到的,忘记是哪里了,效果很不错。唯一有点问题的地方在于,它是通过设置时间轴clock来实现的。所以当设置的entity里面的某些属性用的是callbackProperty方法时候,参数time有时候会变成undefined。

比如,有需求要求,这个自转的地球需要每隔五秒显示一个点的信息。

思路是这样的:数据来源于json文件,使用datasource创建数据集,将里面的entity的show都设置为false不可见。拿到里面的entities,每五秒根据随机数按下标取值,显示,并记录当前显示的entity作为showEntity。每过五秒将showEntity设置为不可见,将新的随机entity赋值给showEntity。第二步,创建用于显示的entity,里面只包含label,每过五秒就将当前的showEntity的position赋值给它。

思路代码大致如下

  let showEntity = 新建好label样式的entity;
  let entities = null;
  let lastEntity = null;
  let currentClock = viewer.clock.currentTime.clone();
// 在加载完成后,再添加监听事件
  viewer.dataSources.add(Cesium.GeoJsonDataSource.load('data/globegangtie.json',{
    markerSize: 20,
    markerColor: Cesium.Color.GREEN,
    markerSymbol: 'city'
  })).then(ds => {
    ds.entities.values.forEach(entity => {
      entity.show = false;
    });
    entities = ds.entities.values;
    viewer.entities.add(labelEntity);
// 也可通过clock的onTrick监听
    viewer.camera.changed.addEventListener(function (percentage) {
      showLabelEntity();    // 变化显示
    });
  })

function showLabelEntity() {
// 时间判断超过5秒
      if (Cesium.JulianDate.secondsDifference(viewer.clock.currentTime,currentClock)/10000 > 5) {
        currentClock = viewer.clock.currentTime.clone();
        lastEntity && (lastEntity.show = false);
        lastEntity = entities[Number.parseInt(entities.length * Math.random())];
        lastEntity.show = true;
        let str = '';
        str += `${lastEntity.properties.project.getValue()}\n${lastEntity.properties.num.getValue()}\n${lastEntity.properties.len.getValue()}`
        labelEntity.position = lastEntity.position.getValue();
        labelEntity.label.text = str;
      }
    }

你可能感兴趣的:(Cesium,GIS,html,html5,css)