004Cesium专刊:动态模型+实时往返轨迹线 您所在的位置:网站首页 cesium模型动画 004Cesium专刊:动态模型+实时往返轨迹线

004Cesium专刊:动态模型+实时往返轨迹线

2024-01-13 01:07| 来源: 网络整理| 查看: 265

前言

人生一迹,谨以此记录Cesium相关系列知识

问题提出:加载动态模型+实时往返轨迹线?

(重点在于实时往返轨迹线)

image.png

一、Cesium加载高德底图

本步骤的目的呢?是为了解决由于网络问题,导致的cesium地球显示不出来的bug,非常影响观感和调试体验。 本文采用高德源,有机会更一个关于各种地图源切换的文章,毕竟底图是万物之本。

let imageryLayers = viewer.imageryLayers; let map = new Cesium.UrlTemplateImageryProvider({ url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}", //高德地图 minimumLevel: 3, maximumLevel: 16, }); imageryLayers.addImageryProvider(map); 二、场景定位

先定位到数据以及模型的最佳视角范围内,以便于后续调试。此处的中心点坐标以及方向hpr参数,可以通过坐标及视角拾取得到,后续文章会更新。

注意:延迟2s,是为了等待场景跳转动画完毕,以便于观察到模型从起点开始移动,不会因为场景动画而影响观看。 viewer.camera.flyTo({ destination : Cesium.Cartesian3.fromDegrees(112.402664,34.621038,4798.56), orientation :{ heading : Cesium.Math.toRadians(351.9), pitch : Cesium.Math.toRadians(-85.7), roll :0.0 } }); setTimeout(2000) 三、轨迹数据结构

轨迹点数据,采用数组方式存储,每个元素代表一个轨迹点信息,每个轨迹点信息采用对象形式存储,其中time表示时间秒数, longitude为轨迹点经度,latitude表示轨迹点纬度。

注意:time表示累加时间(即后轨迹点的time必须大于前轨迹点的time),下列数据意思为:A-B耗时5s,A-B-C耗时10s [ { time: 0, longitude: 112.40641179342809, latitude: 34.6280090036493 }, //A { time: 5, longitude: 112.39561868811835, latitude:34.61691889996022 }, //B { time: 10, longitude: 112.38948151752913, latitude: 34.6286049476503 }, //C { time: 15, longitude: 112.38948151752913, latitude: 34.6286049476503 }, //C { time: 20, longitude: 112.39561868811835, latitude:34.61691889996022 }, //B { time: 25, longitude: 112.40641179342809, latitude: 34.6280090036493 }, //A ] 四、动态模型(模型按照轨迹数据进行运动) 4.1 时间模块设置

此步骤主要是Cesium的时间轴相关设置,包含起始时间、终止时间等

// 起始时间(设置为当前时间) var start = Cesium.JulianDate.fromDate(new Date()); // 终止时间(计算方式,起始时间+30为整个轨迹的总时长) var stop = Cesium.JulianDate.addSeconds(start, 30, new Cesium.JulianDate()); // 设置时间轴开始及结束时间 viewer.clock.startTime = start.clone(); viewer.clock.stopTime = stop.clone(); // 设置时间轴当前时间为起始时间 viewer.clock.currentTime = start.clone(); // 设置时间轴自动播放 viewer.clock.shouldAnimate = true; // 设置到达结束时间后的行为:LOOP_STOP(时间循环);UNBOUNDED(时间继续);CLAMPED(时间暂停) viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 设置时间轴播放的速度 viewer.clock.multiplier = 1; 4.2 轨迹位置插值模块

此步骤主要是Cesium中Property大类中的SampledPositionProperty小类的用法,Property作为Cesium重要类,用法颇深,值得深入研究,有兴趣可以查看大佬vtxf的介绍:[Cesium的Property机制总结](zhuanlan.zhihu.com/p/50534090)

注意:由于返程算法问题,请勿使用其他插值算法( Cesium.LagrangePolynomialApproximation,Cesium.HermitePolynomialApproximation),否则会导致返程算法出问题。若只往程为了讲究路线丝滑,可以采取其他插值算法。 // 设置简单位置属性 var positionSampler = new Cesium.SampledPositionProperty(); //差值器(预防拐弯突兀等问题) positionSampler.setInterpolationOptions({ interpolationDegree: 1, interpolationAlgorithm: Cesium.LinearApproximation }); // 循环写入时间与位置联系 for (var i = 0; i < positionData.length; i++) { var data = positionData[i]; var time = new Cesium.JulianDate.addSeconds(start, data.time, new Cesium.JulianDate()); var position = new Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude); positionSampler.addSample(time, position); } 4.3 加载模型(按轨迹数据移动)

加载模型部分,利用简单的Cesium中Entity加载即可。

注意:模型比例大小,模型方向 var run_entity = viewer.entities.add({ name: 'run', model: { uri: "Data/chick.glb", show: true, scale: 1000, //根据不同模型去设置不同放大比例 }, position: positionSampler, // 设置模型方向(时刻保持路线正前方) orientation : new Cesium.VelocityOrientationProperty(positionSampler) }); 五、实时往返轨迹(往:轨迹一直保持新增,返:重复轨迹会消失)

此处逻辑为本文重点,此处也可以进行更改为自己所需的功能,后续有说明。首先讲解下两个算法,一:获取当前时间点下已走过的轨迹点数组(即往程)。二:删除返程时间点下的冗余轨迹点(即返程)

5.1 构建轨迹管线模型

此步骤为重点中的中点,实时轨迹线是通过CallbackProperty类与动态模型联系,和上文中的SampledPositionProperty类同样,都隶属于Property大类。CallbackProperty类就是一个回调函数,因此可以加入自己所需要的任何功能,回调函数中的参数为time时间

var line_entity = viewer.entities.add({ name: 'line', polylineVolume: { positions: new Cesium.CallbackProperty( (time) => { var runPos = run_entity.position.getValue(time); if(!runPos) return var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(tarpos); var lon = Cesium.Math.toDegrees(cartographic.longitude); var lat = Cesium.Math.toDegrees(cartographic.latitude); let linePos = _getLastPoint(time) linePos = _isRepeatPoint([lon,lat],pos).flat() linePos.push(lon) linePos.push(lat) return Cesium.Cartesian3.fromDegreesArray(linePos) }, false), shape: computeCircle(6.0), material: Cesium.Color.RED, } }) 5.2 获取当前时间点已走过的轨迹数组

原理:通过输入当前时间,计算起始时刻与当前时间的时间差,利用轨迹点的累计时间去对比,时间差大于轨迹点时间,则证明该点已经走过,否则未走过。

function _getLastPoint(time) { const timeDifference = Cesium.JulianDate.secondsDifference(time, start); let currentTime = 0 let lastPointList = [] for (let i = 1; i < positionData.length; i++) { currentTime = positionData[i].time if (timeDifference < currentTime) { lastPointList.push([positionData[i - 1].longitude,positionData[i - 1].latitude]) break } else { lastPointList.push([positionData[i - 1].longitude,positionData[i - 1].latitude]) } } return lastPointList } 5.3 获取当前时间点已走过两遍的轨迹数组(即从返程数组里去除往程数组)

原理:通过输入当前时间,计算起始时刻与当前时间的时间差,利用轨迹点的累计时间去对比,时间差大于轨迹点时间,则证明该点已经走过,否则未走过。 判断方法:首先让轨迹数组组成轨迹线段,然后去判断当前时间点是都在轨迹线段上,若在,则证明该轨迹线段的终点应删除,若不在,则保留(注意该方法部分特殊情况会有瑕疵)

// 判断管线点组中是否有冗余点,有冗余点则删除冗余点后的元素 // 输入:轨迹点,管线点组 function _isRepeatPoint(point, posList) { if(posList.length == 1) return posList.flat() for (let i = 1; i < posList.length; i++) { //斜率不存在 if (posList[i][0] == posList[i - 1][0]) { if(point[0] == posList[i][0]){ posList.splice(i) return posList.flat() } } else { // 斜率为0 if(posList[i][1] == posList[i-1][1]){ if(point[1] == posList[i][1]){ posList.splice(i) return posList.flat() } } else { // 正常斜截式 k = (posList[i][1]-posList[i-1][1])/(posList[i][0]-posList[i-1][0]) b = posList[i][1] - k*posList[i][0] if (point[1].toFixed(4) == (k*point[0] + b).toFixed(4)) { posList.splice(i) return posList.flat() } } } } return posList.flat() } 六、全部代码及效果图

004实时往返轨迹线.gif

动态模型+实时往返轨迹线demo let viewer = new Cesium.Viewer('cesiumContainer'); // 更换底图 let imageryLayers = viewer.imageryLayers; let map = new Cesium.UrlTemplateImageryProvider({ url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}", //高德地图 minimumLevel: 3, maximumLevel: 16, }); imageryLayers.addImageryProvider(map); //添加地图贴图 // 场景定位 viewer.camera.flyTo({ destination : Cesium.Cartesian3.fromDegrees(112.402664,34.621038,4798.56), orientation :{ heading : Cesium.Math.toRadians(351.9), pitch : Cesium.Math.toRadians(-85.7), roll :0.0 } }); setTimeout(2000) // 定义中转点数据 var positionData = [ { time: 0, longitude: 112.40641179342809, latitude: 34.6280090036493 }, //A { time: 5, longitude: 112.39561868811835, latitude:34.61691889996022 }, //B { time: 10, longitude: 112.38948151752913, latitude: 34.6286049476503 }, //C { time: 15, longitude: 112.38948151752913, latitude: 34.6286049476503 }, //C { time: 20, longitude: 112.39561868811835, latitude:34.61691889996022 }, //B { time: 25, longitude: 112.40641179342809, latitude: 34.6280090036493 }, //A ]; // 起始时间 var start = Cesium.JulianDate.fromDate(new Date()); // 计算位置差值 var positionSampler = new Cesium.SampledPositionProperty(); //差值器(预防拐弯突兀等问题) positionSampler.setInterpolationOptions({ interpolationDegree: 1, interpolationAlgorithm: Cesium.LinearApproximation }); for (var i = 0; i < positionData.length; i++) { var data = positionData[i]; var time = new Cesium.JulianDate.addSeconds(start, data.time, new Cesium.JulianDate()); var position = new Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude); positionSampler.addSample(time, position); } // 创建一个模型对象 var fireMan_entity = viewer.entities.add({ name: 'model', model: { uri: "Data/chick_run.glb", show: true, scale: 2000, }, position: positionSampler, orientation : new Cesium.VelocityOrientationProperty(positionSampler) }); // 创建一个轨迹线对象 var fireHose_entity = viewer.entities.add({ name: 'line', polylineVolume: { positions: new Cesium.CallbackProperty( (time) =>{ var tarpos = fireMan_entity.position.getValue(time); if(!tarpos) return var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(tarpos); var lon = Cesium.Math.toDegrees(cartographic.longitude); var lat = Cesium.Math.toDegrees(cartographic.latitude); let pos = _getLastPoint(time) pos = _isRepeatPoint([lon,lat],pos).flat() pos.push(lon) pos.push(lat) return Cesium.Cartesian3.fromDegreesArray(pos) }, false), shape: computeCircle(20.0), material: Cesium.Color.RED, } }) // 设置视角跟随物体运动,并显示信息框 // viewer.trackedEntity = fireMan_entity; // 定义时钟参数 var stop = Cesium.JulianDate.addSeconds(start, 30, new Cesium.JulianDate()); viewer.clock.startTime = start.clone(); viewer.clock.stopTime = stop.clone(); viewer.clock.currentTime = start.clone(); viewer.clock.shouldAnimate = true; viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; viewer.clock.multiplier = 1; // 判断当前时间下,经过的中转点,构成管线点组 function _getLastPoint(time) { const timeDifference = Cesium.JulianDate.secondsDifference(time, start); let currentTime = 0 let lastPointList = [] for (let i = 1; i < positionData.length; i++) { currentTime = positionData[i].time if (timeDifference < currentTime) { lastPointList.push([positionData[i - 1].longitude,positionData[i - 1].latitude]) break } else { lastPointList.push([positionData[i - 1].longitude,positionData[i - 1].latitude]) } } return lastPointList } // 判断管线点组中是否有冗余点,有冗余点则删除冗余点后的元素 // 输入:轨迹点,管线点组 function _isRepeatPoint(point, posList) { if(posList.length == 1) return posList.flat() for (let i = 1; i < posList.length; i++) { //斜率不存在 if (posList[i][0] == posList[i - 1][0]) { if(point[0] == posList[i][0]){ posList.splice(i) return posList.flat() } } else { // 斜率为0 if(posList[i][1] == posList[i-1][1]){ if(point[1] == posList[i][1]){ posList.splice(i) return posList.flat() } } else { // 正常斜截式 k = (posList[i][1]-posList[i-1][1])/(posList[i][0]-posList[i-1][0]) b = posList[i][1] - k*posList[i][0] if (point[1].toFixed(4) == (k*point[0] + b).toFixed(4)) { posList.splice(i) return posList.flat() } } } } return posList.flat() } // 计算轨迹管道函数 function computeCircle(radius) { const positions = []; for (let i = 0; i < 360; i++) { const radians = Cesium.Math.toRadians(i); positions.push( new Cesium.Cartesian2( radius * Math.cos(radians), radius * Math.sin(radians) ) ); } return positions; }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有