Bootstrap

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


先总结一下要实现什么效果。
轨迹回放,轨迹回放。 一个物体根据设定的轨迹进行移动,并且把移动后的路径绘制出来。
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: 因为要绘制运动后的轨迹线,所以用回调的方式把走过的路径重新绘制
;