2021-09-22
在 VS Code 中实现Cesium的代码提示,参照链接:VSCode实现Cesium的自动提示功能_liveanddie的博客-CSDN博客
为了防止这片博文链接失效,特此记录。
在Cesium 的源码中,找到CesiumUnminified文件夹中的Cesium.d.ts文件,将其文件放到项目中去。然后在需要代码提示的文件开头,用以下代码引用:
/// <reference path="../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的官方文档,这个方法是用来确定图片如何在椭圆球体上展开的。
主要是这两个参数: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的最小缩放和最大缩放。
接下来就是处理旋转功能了。当时创建的时候,并没有为entity.model设置 orientation这个值。导致后面我想要赋值的时候,直接给我报错了。如果放置的模型后续涉及到旋转操作,这个属性就必须要设置。主要定义俯仰角,官方的示例是这个:
我使用的俯仰角和旋转体系都是官方提供的那个方法,在官方文档中还有很多。我使用的是
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值,而位置在地球上需要一个三维的笛卡尔坐标值。所以需要用到这个方法。获取射线,然后再找到这个射线和地球的焦点。
同事并没有直接给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)。
这是个回调方法,每次要获取值的时候,就调用这个方法去更新。哪些地方能用呢,这个看entity里面的每一个graphic就知道了。拿线段polyline来举例。
只要官方文档说,这个值可以是一个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 = $('<div></div>');
// 加入名称
box.append($('<div>'+item.name+'</div>'));
// 加入距离,这里设置一个变量,方便后续的callbackProperty里面可以直接更改。就不用单独设置一个id了。
const distance = $('<div>'+item.distance+'</div>');
// 将这个盒子加入左侧列表
$('#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<pathArr.length;i++){
const lon = pathArr[i];
const lat = pathArr[i];
// 时间
let currentTime = Cesium.JulianDate.addSeconds(startTime .clone(), (i-1 || 0) * timesel, new Cesium.JulianDate());
currentTime = Cesium.JulianDate.addSeconds(currentTime , j * timesel, new Cesium.JulianDate());
// 位置
let position = null;
if (lng && lat) position = Cesium.Cartesian3.fromDegrees(lng, lat, 0);
if (position && time)
property.addSample(time, position);
// 记录结束时间
if(i == pathArr.length - 1){
endiTime = currentTime;
}
}
let changText = '需要动态变化的文本'
function getText(){
return changText ;
}
// 创建entity
const entity = viewer.entities.add({
positions: property,
label: {
text: new Cesium.CallbackProperty(getText, false),
......其他配置
},
model:{
模型数据
}
});
// 设置时间轴
viewer.clock.startTime = startTime .clone();
viewer.clock.stopTime = endiTime .clone();
viewer.clock.currentTime = startTime .clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
// 设置时间轴监听事件
var timeClockTickCallBackFun=function (clocktick) {
// 确保时间轴的时间,小于我们设置好的停止时间
if (Cesium.JulianDate.lessThanOrEquals(clocktick.currentTime, stop.clone())) {
// 当前时间点,entity的坐标
let cat=entity.position.getValue(clocktick.currentTime)
let ellipsoid = viewer.scene.globe.ellipsoid
let cartographic = ellipsoid.cartesianToCartographic(cat);
let clat = Cesium.Math.toDegrees(cartographic.latitude);
let clon = Cesium.Math.toDegrees(cartographic.longitude);
// 计算离上一个坐标的距离 算法根据需要来变
let dis=Math.sqrt(Math.pow((clon-lastlon),2)+Math.pow((clat-lastlat),2));
dis+=lastDis ;
lastDis =dis;
lastLon =clon;
lastLat =clat;
changText = '有了dis距离,想要变化的文字';
// 时间回到了开始时间,达成重置条件
if (Cesium.JulianDate.equalsEpsilon(clocktick.currentTime, endiTime .clone(),0.1)){
lastlon=lon;
lastlat=lat;
lastdis=0;
}
}
}
viewer.clock.onTick.addEventListener(timeClockTickCallBackFun);
因为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;
}
}