拉格朗日插值平滑三维折线[cesium][LagrangePolynomialApproximation]

使用拉格朗日插值平滑曲线

1 平滑前后效果对比:

平滑前

平滑前

平滑后

平滑后

2 拉格朗日插值代码:(参考cesium源码)

结合拉格朗日原理:https://zhuanlan.zhihu.com/p/135229305
例子参考

源码如下:https://github.com/CesiumGS/cesium/blob/b30/Source/Core/LagrangePolynomialApproximation.js#L39

    /**
     * Interpolates values using Lagrange Polynomial Approximation.
     *
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number[]} [result] An existing array into which to store the result.
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    LagrangePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
        if (!defined(result)) {
            result = new Array(yStride);
        }

        var i;
        var j;
        var length = xTable.length;

        for (i = 0; i < yStride; i++) {
            result[i] = 0;
        }

        for (i = 0; i < length; i++) {
            var coefficient = 1;

            for (j = 0; j < length; j++) {
                if (j !== i) {
                    var diffX = xTable[i] - xTable[j];
                    coefficient *= (x - xTable[j]) / diffX;
                }
            }

            for (j = 0; j < yStride; j++) {
                result[j] += coefficient * yTable[i * yStride + j];
            }
        }

        return result;
    };

3 cesium中实现代码

放入https://sandcastle.cesium.com/index.html?src=Callback%20Property.html运行即可

// This example illustrates a Callback Property, a property whose
// value is lazily evaluated by a callback function.
// Use a CallbackProperty when your data can't be pre-computed
// or needs to be derived from other properties at runtime.
var viewer = new Cesium.Viewer("cesiumContainer");
viewer.clock.shouldAnimate = true;

var startLatitude = 35;
var startLongitude = -120;
var endLongitude;
var startTime = Cesium.JulianDate.now();
var stTime = startTime;


// Add a polyline to the scene. Positions are dynamic.
var isConstant = false;
var redLine = viewer.entities.add({
  polyline: {
    // This callback updates positions each frame.
    positions: new Cesium.CallbackProperty(function (time, result) {
      endLongitude =
        startLongitude +
        0.001 * Cesium.JulianDate.secondsDifference(time, startTime);
      
      // 平滑前效果
      // return computeCirclularFlight(-112.110693, 36.0994841, 0.03, time);
      // 平滑后效果
      return computeCirclularFlightSmoothly(-112.110693, 36.0994841, 0.03, 500, stTime, time);
      /*
      return Cesium.Cartesian3.fromDegreesArray(
        [startLongitude, startLatitude, endLongitude, startLatitude],
        Cesium.Ellipsoid.WGS84,
        result
      );
      */
    }, isConstant),
    width: 5,
    material: Cesium.Color.RED,
  },
});


//Generate a random circular pattern with varying heights.
function computeCirclularFlight(lon, lat, radius, curTime) {

        // let a = JulianDate.secondsDifference(JulianDate.now(), stTime);
         let positions = [];
        // Generate a random circular pattern with letying heights.
        let property = new Cesium.SampledPositionProperty();
        for (let i = 0; i <= 360; i += 45) {
            let radians = Cesium.Math.toRadians(i);
            let time = Cesium.JulianDate.addSeconds(
                stTime,
                i,
                new Cesium.JulianDate()
            );
        
            let position = Cesium.Cartesian3.fromDegrees(
                lon + radius * 1.5 * Math.cos(radians),
                lat + radius * Math.sin(radians),
                Cesium.Math.nextRandomNumber() * 500 + 
                    Math.random() * 20 * Cesium.JulianDate.secondsDifference(curTime, stTime)
            );
        
            positions.push(position);
            property.addSample(time, position);
      }

      return positions;
}

    // Generate a random circular pattern with varying heights.
    function computeCirclularFlightSmoothly(lon, lat, radius, levelHeight, stTime, curTime) {

        // let a = JulianDate.secondsDifference(JulianDate.now(), stTime);
        let positions = [];
        let llhPositions = [];
        // let property = new PositionProperty();
        // Generate a random circular pattern with letying heights.
        for (let i = 0; i <= 360; i += 45) {
            let radians = Cesium.Math.toRadians(i);
            let time = Cesium.JulianDate.addSeconds(
                stTime,
                i,
                new Cesium.JulianDate()
            );

            let tmpPoint = new Cesium.Cartographic(
                lon + radius * 1.5 * Math.cos(radians),
                lat + radius * Math.sin(radians),
                Cesium.Math.nextRandomNumber() * 0.1 * levelHeight + levelHeight +
                    Cesium.Math.random() * 20 * Cesium.JulianDate.secondsDifference(curTime, stTime)
            );
            llhPositions.push(tmpPoint);
            positions.push(
                Cesium.Cartesian3.fromDegrees(
                    tmpPoint.longitude,
                    tmpPoint.latitude,
                    tmpPoint.height
            ));
        }
 
        // let iptPositions = Array<Cartographic>();
        let iptPositions = [];
        let xRes = [];
        let xTable = [];        // interpolate at xTable[i]
        for(let ix = 0; ix < positions.length; ++ix) {
            xTable.push(ix * 100);
        }
        let yTable = [];        // used to interpolate, in {la1, lon1, h1, l2, lon2, h2}
        for(let iy = 0; iy < positions.length; ++iy) {
            yTable.push(llhPositions[iy].longitude);
            yTable.push(llhPositions[iy].latitude);
            yTable.push(llhPositions[iy].height);
        }
        let yStride = 3;        // 3 dependent vaule in yTable is viewed as one item
        
        for (let ix = 0; ix < xTable[positions.length - 1]; ++ix) {
            let iptRes = [];
            Cesium.LagrangePolynomialApproximation.interpolateOrderZero(
                ix,
                xTable,
                yTable,
                yStride,
                iptRes
            );

            iptPositions.push(
                Cesium.Cartesian3.fromDegrees(
                iptRes[0],
                iptRes[1],
                iptRes[2]
            ));
        }

        return  iptPositions;
    }

// Keep the view centered.
viewer.trackedEntity = redLine;

<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.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值