【Cesium】轨迹回放:一个物体根据设定的轨迹进行移动,并且把移动后的路径绘制出来

本文介绍了使用Cesium实现轨迹回放的方法。首先对路径进行插值处理以避免模型“瞬移”,接着添加模型,再用new Cesium.SampledPositionProperty()构造位置与时间的关系,最后根据时间动态绘制走过的线。还提及了轨迹回放的主要思想及用到的关键方法。


先总结一下要实现什么效果。
轨迹回放,轨迹回放。 一个物体根据设定的轨迹进行移动,并且把移动后的路径绘制出来。
cesium中实现动画还得是 时间轴
跟时间关联起来,在某个时间是某个状态,不同的时间是不同的状态,那不就动起来了?

首先得有一个路径吧

路径会涉及到一个比较关键的问题,就是如果传进来的路径稀疏了,会有模型在两个点之间“瞬移”的现象,不够顺滑,所以要对路径进行插值处理。
这里没有把路径绘制出来,只是把模型“走过”的路径绘制出来了,这样视觉效果更好些。
所以插值得到了一个密集的轨迹。插值的方法还是比较简单的,可以看这里。假设我们已经得到了(interpolatedCarCoordinates)。

然后得有模型吧

cesium添加一个物体还是比较简单的,我们先加一个模型。

/** 构造一个位置会随时间变换的model
 * @param position: 模型的位置
 * @param modelPath:模型的文件路径*/
function myModel(position, modelPath='') {
    return {
        position: position,
        // 根据所提供的速度计算模型的朝向
        orientation: new Cesium.VelocityOrientationProperty(position),
        model: {
            uri: modelPath,
            minimumPixelSize: 50,
            scale: 0.5,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
        },
    }
}

然后就是把时间位置关联起来

路径、模型都有了,那怎么让模型跟着路径动起来?之前说了时间轴
cesium中,可以用new Cesium.SampledPositionProperty()(该方式的使用方式看这里)来构造位置与时间的关系。

所以我们就根据时间和位置来构造一个好了。

/** 为方便使用,传进来的是路径是经纬度[[经度,纬度]...[经度,纬度]]的形式*/
/* 假设已经根据插值获得了一个密集的路线坐标interpolatedCarCoordinates(转换成Cartographic形式,为了后面使用)*/

// 首先我们要根据经纬度获得一个加了地形的真实坐标 假设是realPosition
function getRealPosition(terrain, interpolatedCarCoordinate) {
    return Cesium.sampleTerrainMostDetailed(terrain, interpolatedCarCoordinate);
}

/** 虽然我们插值得到了比较均匀的点而且也是真实的,但是只是经纬度上是均匀的,因为加了地形所以在距离上不是均匀的,所以如果以同等时间在两个点之间移动会发现速度不一样
 * 所以对距离进行计算,得到distanceArray,用来计算一个点到下一个点之间该用多少时间*/
function getSpaceDistance(CarCoordinates) {
    let distanceArray = []
    for (let i = 0; i < CarCoordinates.length - 1; i++) {
        // 初始化测地线
        let geodesic = new Cesium.EllipsoidGeodesic();
        // 设置测地线的起点和终点
        geodesic.setEndPoints(CarCoordinates[i], CarCoordinates[i + 1]);
        let s = geodesic.surfaceDistance;
        //返回两点之间的距离
        s = Math.sqrt(Math.pow(s, 2) + Math.pow(CarCoordinates[i + 1].height - CarCoordinates[i].height, 2));
        distanceArray.push(s.toFixed(3))
    }
    return distanceArray;
}
/**
 * 计算一个点到下一个点之间该用多少时间*/
function getTime(distanceArray) {
    const timeInterval = 1 // 没开倍速就是1 
    const speed = 6 //速度 m/s
    let p
    let timeArray = [0]
    for (let i = 0; i < distanceArray.length; i++) {
        p = distanceArray[i]
        let time = (timeInterval / speed) * (p)
        timeArray.push(time)
    }
    return timeArray
}

通过以上操作我们得到了时间间隔数组timeArray和位置数组realPosition

/** 到了真正关联起来的时候了*/
function timePosition(startTime, timeArray, realPosition) {
    let cartesian3Model = [] //呆会儿要用来绘制走过的路的
    let timeList = [] // 呆会儿要用来绘制走过的路的
    let times = 0   // 在某个点的时间 最开始在起始位置,是0 所以模型在某位置的真实时间是当前的时间(startTime)加上时间间隔
    let modelProperty = new Cesium.SampledPositionProperty()    //构造模型位置和时间关系的

    for (let i = 0; i < realPosition.length; i++) {
        times = times + timeArray[i]
        let time = Cesium.JulianDate.addSeconds(startTime, times, new Cesium.JulianDate);
        timeList.push(time)

        /** 模型的坐标数据*/
        let pCartesian3Model = radToCar3(realPosition[i])
        cartesian3Model.push(pCartesian3Model)
        modelProperty.addSample(time, pCartesian3Model);
    }
    return {afterRoute: cartesian3Model, timeList: timeList}
}

function radToCar3(value) {
// 把坐标转换成Cartesian3
        return Cesium.Cartesian3.fromRadians(value.longitude, value.latitude, value.height)
    }

绘制走过的线

刚刚在将时间和位置关联起来的时候已经知道了模型走过的路和时间之间的关系,所以只要根据时间把路径绘制出来就行。
因为是时间变化后,动态的绘制,所以就把线的位置设置成回调。时间改变就把路线push进去。

/** 获取模型走过的位置*/
function getRoutePosition(model, cartesian3Model, timeList) {
    let linePoints = null
    let stopTime = timeList[timeList.length - 1]    //模型最后的时间
    let lastPosition = cartesian3Model[cartesian3Model.length - 1]  //模型最后的位置

    return new Cesium.CallbackProperty((time, result) => {
        if (time >= stopTime) {
            model.position = lastPosition
            linePoints = cartesian3Model
        } else {
            // 模型在这个时间点所在的位置不一定是在路径数组里面,毕竟我们插的值还是没那么密集
            let modelCartesian3 = model.position.getValue(time);
            let index = timeList.findIndex(function (value) {
                return value.secondsOfDay >= time.secondsOfDay
            })
            //避免在浏览器没打开的时候没有监听到时间改变,导致这段时间没有坐标,路线直接跳到后面
            // 所以直接把之前走过的位置赋值给线,然后再把当前模型在的位置push进去
            linePoints = cartesian3Model.slice(0, index)
            linePoints.push(modelCartesian3)
        }
        result = linePoints;
        return result
    }, false);
}

function myRoute(routePosition) {
    return {
        type: 'line',
        polyline: {
            positions: routePosition,
            material: Cesium.Color.fromCssColorString('red'),
            clampToGround: true,
            width: this.width
        },
    }
}

总结

大致实现就是这些。要设置不同的样式什么的也可以自己设置。具体使用就是一个一个调用就行了。注意getRealPosition() 返回的是一个Promise对象,需要.then一下,这一步如果路径点太多的话会卡顿,还不知道怎么解决。 其他的就暂时不写了,是根据以前做的整理出来的,之前做的是接口,涉及和考虑的东西有点多,把这部分比较关键的抽出来了。

轨迹回放的主要思想是让模型的位置随时间来进行变换,并且让视角的中心一直跟着模型移动
主要用到的方法:

  • 1、trackedEntity: 相机跟踪模型
  • 2、clock {
    multiplier: 控制播放速度
    shouldAnimate: 设置是否移动
    clockRange: 时间戳到终点后停止还是循环
    }
  • 3、SampledPositionProperty: addSample() 用来构造时间和位置的关系 根据这个关系来移动模型和绘制路径
  • 4、sampleTerrainMostDetailed: 用来获取加载了地形后的真实坐标
  • 5、Cesium.CallbackProperty: 因为要绘制运动后的轨迹线,所以用回调的方式把走过的路径重新绘制
<think>我们正在讨论如何使用Cesium的SampledPositionProperty来实现模型移动轨迹生成。根据引用[1]和引用[2]提供的信息,SampledPositionProperty类用于定义随时间变化的位置属性,它支持插值,可以用于实现平滑的移动轨迹。同时,引用[2]还提到了一个案例:三维模型的轨迹运动(动画控制),其中使用了setInterpolationOptions方法来设置插值选项。 实现步骤大致如下:1.创建一个SampledPositionProperty实例来存储模型的位置随时间的变化。 2.向这个实例中添加时间-位置样本点(即关键帧)。 3.设置插值选项,以控制样本点之间的运动方式(如线性、拉格朗日等)。 4.将模型实体的position属性设置为这个SampledPositionProperty实例。 5.使用Cesium的动画控件(如viewer.clock)来控制动画的播放,模型将沿着样本点定义的路径移动。 6.同时,我们可以使用Cesium的PolylineGeometry或Entity.polyline来绘制运动轨迹。下面我们详细说明实现方法: ###1.创建SampledPositionProperty实例```javascript//创建位置属性实例varpositionProperty= newCesium.SampledPositionProperty();//设置插值选项,这里使用线性插值,也可以使用其他插值方式positionProperty.setInterpolationOptions({interpolationDegree:1,//1表示线性插值,5表示使用5阶多项式插值(如拉格朗日插值)interpolationAlgorithm: Cesium.LinearApproximation //线性插值算法}); ```###2.添加时间-位置样本点我们需要在指定的时间点添加位置(Cartesian3对象)。例如,我们定义两个时间点,模型从起点移动到终点: ```javascriptvar startTime =Cesium.JulianDate.fromIso8601("2020-01-01T00:00:00Z");var stopTime =Cesium.JulianDate.addSeconds(startTime,10,new Cesium.JulianDate());//添加起点varstartPosition= Cesium.Cartesian3.fromDegrees(-75.59777,40.03883,1000);positionProperty.addSample(startTime, startPosition);//添加终点varendPosition= Cesium.Cartesian3.fromDegrees(-75.59678,40.03828,1000); positionProperty.addSample(stopTime, endPosition);``` ###3.创建模型实体并设置位置属性```javascriptvar entity= viewer.entities.add({name: 'MovingModel',position:positionProperty, //绑定位置属性model: {uri: 'path/to/model.glb',//替换为你的模型路径minimumPixelSize:128,maximumScale:20000}}); ```###4.设置动画时间线为了让动画在指定的时间范围内播放,我们需要设置时钟:```javascriptviewer.clock.startTime =startTime.clone();viewer.clock.stopTime= stopTime.clone(); viewer.clock.currentTime =startTime.clone();viewer.clock.clockRange =Cesium.ClockRange.LOOP_STOP; //到达结束时间后停止viewer.timeline.zoomTo(startTime, stopTime);//时间轴缩放到这个范围```###5.绘制运动轨迹我们可以通过创建一个Polyline实体来绘制轨迹。注意,轨迹需要从位置属性中获取所有样本点(或者我们可以自己存储这些点用于绘制)。```javascript//方法1:直接使用SampledPositionProperty中的样本点varsamples =positionProperty._property._values; //注意:这是内部属性,可能不推荐使用//方法2:我们在添加样本点时自己存储位置点var positions= [startPosition, endPosition];//这里只有起点和终点,如果有多个点,需要自己维护数组//创建轨迹线varpolyline =viewer.entities.add({polyline: {positions: positions,width:5,material: Cesium.Color.RED} });``` 但是,如果样本点很多,我们可能希望动态生成轨迹。我们可以使用Cesium的PolylineGeometry来绘制,或者使用Entity.polyline的positions属性设置为一个PositionProperty(例如,我们可以使用SampledPositionProperty来生成轨迹,但轨迹是静态的,即一开始就画好整个路径)。如果我们希望轨迹随着模型移动而动态绘制,则需要更复杂的方法(例如,使用CallbackProperty动态计算当前位置之前的路径)。###动态轨迹绘制(可选)动态轨迹绘制是指随着模型移动轨迹从起点逐渐延伸到模型当前位置。这可以通过CallbackProperty来实现,每次回调时返回从起点到当前时间点的所有位置样本(或插值点)。但是,这可能会对性能产生影响,因为每一帧都需要计算。另一种简单的方法是,在模型移动过程中,实时记录经过的位置点,并更新到Polyline的positions数组中。这可以通过在时钟的onTick事件中不断添加当前位置来实现。 ```javascriptvar dynamicPositions= []; //用于存储轨迹点dynamicPositions.push(startPosition); //加入起点//监听时钟的onTick事件viewer.clock.onTick.addEventListener(function(clock) {//获取当前时间var currentTime =clock.currentTime;//获取模型当前位置varcurrentPosition= positionProperty.getValue(currentTime);//将当前位置加入轨迹点数组dynamicPositions.push(currentPosition);//更新polyline的positionspolyline.polyline.positions= dynamicPositions; });``` 注意:这种方法在每一帧都会更新整个Polyline,如果轨迹很长可能会影响性能。我们可以考虑使用GeometryInstance和Primitive来高效更新,但这更复杂。###完整示例代码以下是一个简单的完整示例(假设已经有一个Cesium Viewer实例): ```javascript//创建位置属性var positionProperty =new Cesium.SampledPositionProperty(); positionProperty.setInterpolationOptions({interpolationDegree:1,interpolationAlgorithm: Cesium.LinearApproximation}); //设置时间var startTime =Cesium.JulianDate.now();var stopTime =Cesium.JulianDate.addSeconds(startTime,10,new Cesium.JulianDate());//添加位置样本var startPosition =Cesium.Cartesian3.fromDegrees(-75.59777,40.03883,1000); positionProperty.addSample(startTime,startPosition);var endPosition =Cesium.Cartesian3.fromDegrees(-75.59678,40.03828,1000); positionProperty.addSample(stopTime, endPosition);//创建模型实体var entity= viewer.entities.add({position: positionProperty,model: {uri: 'path/to/model.glb',minimumPixelSize:128}}); //设置时钟viewer.clock.startTime= startTime;viewer.clock.stopTime= stopTime;viewer.clock.currentTime= startTime;viewer.clock.clockRange =Cesium.ClockRange.LOOP_STOP;//创建轨迹线(静态的,只有起点和终点)var positions= [startPosition, endPosition];var polyline =viewer.entities.add({polyline: {positions: positions,width:5,material:Cesium.Color.RED}}); //如果需要动态轨迹,则使用以下代码替换上面的polyline创建和更新vardynamicPositions =[startPosition]; vardynamicPolyline =viewer.entities.add({polyline: {positions: newCesium.CallbackProperty(function(time,result){return dynamicPositions;}, false),width:5,material:Cesium.Color.RED}}); viewer.clock.onTick.addEventListener(function(clock){var currentTime =clock.currentTime;if(Cesium.JulianDate.compare(currentTime, stopTime)>0){return; //如果当前时间超过结束时间,不再添加}varcurrentPosition= positionProperty.getValue(currentTime);dynamicPositions.push(currentPosition); });``` 以上代码实现了模型移动轨迹生成(静态和动态两种方式)。请根据实际需求选择轨迹绘制方式。 §§相关问题§§1.如何为SampledPositionProperty添加多个关键帧(多个时间点的位置)? 2.如何调整模型的移动速度?3.除了线性插值,Cesium还支持哪些插值算法?它们的效果有何不同? 4.如何让模型在移动过程中始终朝向运动方向?</think>### Cesium 中实现模型移动轨迹生成的方法 在 Cesium 中使用 `SampledPositionProperty` 实现模型移动轨迹生成需要以下步骤: #### 1. 创建位置属性对象 ```javascript const positionProperty = new Cesium.SampledPositionProperty(); ``` #### 2. 添加时间-位置样本点 ```javascript const startTime = Cesium.JulianDate.now(); const positions = [ Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100), // 北京 Cesium.Cartesian3.fromDegrees(121.47, 31.23, 100), // 上海 Cesium.Cartesian3.fromDegrees(113.26, 23.12, 100) // 广州 ]; // 添加位置样本(每10秒一个点) positions.forEach((pos, i) => { const time = Cesium.JulianDate.addSeconds( startTime, i * 10, new Cesium.JulianDate() ); positionProperty.addSample(time, pos); }); ``` #### 3. 设置插值算法(平滑轨迹) ```javascript positionProperty.setInterpolationOptions({ interpolationDegree: 5, // 插值阶数(越高越平滑) interpolationAlgorithm: Cesium.HermitePolynomialApproximation // 埃尔米特插值 }); ``` #### 4. 创建移动模型实体 ```javascript const movingModel = viewer.entities.add({ position: positionProperty, model: { uri: 'path/to/model.glb', minimumPixelSize: 128 }, orientation: new Cesium.VelocityOrientationProperty(positionProperty) // 自动朝向 }); ``` #### 5. 生成运动轨迹 ```javascript const pathEntity = viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { return positions; // 返回轨迹点数组 }, false), width: 5, material: Cesium.Color.RED } }); ``` #### 6. 控制动画播放 ```javascript // 设置时间范围 viewer.clock.startTime = startTime; viewer.clock.stopTime = Cesium.JulianDate.addSeconds( startTime, positions.length * 10, new Cesium.JulianDate() ); // 开启循环播放 viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; viewer.timeline.zoomTo(viewer.clock.startTime, viewer.clock.stopTime); ``` #### 关键参数说明: 1. **插值算法选择**: - `LinearApproximation`:线性插值(折线路径) - `LagrangePolynomialApproximation`:拉格朗日插值(平滑曲线) - `HermitePolynomialApproximation`:埃尔米特插值(带速度控制的平滑曲线) 2. **轨迹优化技巧**: - 使用`CallbackProperty`动态更新轨迹 - 通过`sampleRate`控制采样密度: ```javascript Cesium.SampledPositionProperty.prototype.getValue = function(time, result) { // 自定义采样逻辑 }; ``` - 添加高度偏移避免模型与轨迹重叠: ```javascript const offsetPositions = positions.map(p => Cesium.Cartesian3.add(p, new Cesium.Cartesian3(0, 0, 50), new Cesium.Cartesian3()) ); ``` ### 实际应用效果 实现后模型将沿预定路径平滑移动,同时显示红色轨迹线。通过`viewer.trackedEntity = movingModel`可使镜头跟随模型,通过`clock.multiplier`可调整移动速度[^1][^2]。
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值