Real-time path interpolation

Hi guys,

Currently I’m working on CesiumJS project where I show real-time airplane movement in 3D. Since I don’t have “predefined” path, I’m adding coords in real-time.

I want aircraft to move smoothly, so I’m using Interpolation Algorithm, provided by Cesium. But result path isn’t really smooth. After adding new point, looks like path curve is recalculated, so aircraft changes position randomly. Sometimes my Entity goes behind point, and then turns around after couple a seconds. This happens even with interpolationDegree=2(and I always have at least 2 path points ahead). As far as I understand, this is a problem of all Polynomial Interpolation algorithms. They are working good on existing data, not dynamically updated.

After some googling, I understood that possible solutions is to interpolate path with splines. But I ran into two problems:

  1. I couldn’t find spline class what supports adding knots without changing previous part of curve,

  2. I couldn’t find the way to use spline as position for entity, except manually evaluate spline for some interval and set entity position(this would cause huge memory and cpu consumption)

May be somebody had similar problems and got a solution?

Thanks in advance.

You can use CZML to implement this. Here is an example in sand castle as you described:

https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Interpolation.html&label=Showcases

Hi Aaron,

Thank you for your answer.

What’s the difference between CZML and direct API? I thought they are same.

Regarding interpolation example: yes I have seen it, and as I said: interpolation works very badly when you add points dynamically - I experience random turnovers of entity, position changing when adding points. I think I’m getting problems related from this link: https://en.wikipedia.org/wiki/Runge’s_phenomenon .

Thanks.

1 Like

This example is using offline data.

I have real time data which is adding more coordinates in every 30 seconds or less.

Thanks
lg

This example is using offline data.

I have real time data which is adding more coordinates in every 30 seconds or less.

Thanks
lg

Hello, did you found how to use interpolation with realtime data?...

kind regards
Philippe

I think this should work:

I couldn’t find the way to use spline as position for entity, except manually evaluate spline for some interval and set entity position(this would cause huge memory and cpu consumption)

I don’t think this would be a huge memory or CPU issue. Especially if you only recompute the path every time you add a new point, and you use a callback property for the position which only gets updated every time it’s needed.

Here’s an example of a polyline that grows dynamically with a callback property:

https://cesiumjs.org/Cesium/Build/Apps/Sandcastle/?src=Callback%20Property.html

Hello, that not working for me, the end position is build with time depend in this example.

My need is to have smooth entity position change with data position received each 0.5 seconds...

any idea?

You can update the positions dynamically in the callback property. They don’t have to be known ahead of time. The drawing on terrain Sandcastle example does this to update the polygon’s positions from user input:

This forum thread is also talking about updating entity’s dynamically with data coming every second from a server:

https://groups.google.com/d/msg/cesium-dev/wtcrYum0dTA/GbM735P2CAAJ

If these aren’t helping feel free to post a Sandcastle example of your code/attempt (you can click “Share” and paste the link here so I can run it) and I can try and take a look.

Hello Omar,

Thank you very much for your help.

I will check on both link to see if I can use it.

Waiting that, I share here my code.

At this time it does:

- realtime position on aircraft (0.5 second refresh)
           to use it, replace the registration string variable to an active airplane from flightradar24.com for example.

- chasse view of the airplane depending of the track of the airplane

here the JavaScript code:

var newLat=49.451413;
var newLong=2.111137;
var track_old=0
var delta_track=0;
var track=0;
var res4=49.451413; //latitude_value
var res5=2.111137; // longitude_value
var res6=0;
var res7=150;
var res8=0;
var res9=0;
var timer=0;
var camtime=Date.now();
var datatime=0;
var datatimeold=0;
var cameraAngle = 1;
var dataAvailable=0;
var time = Date.now();
var timerimmat= 0;
var registration= 'EI-ISB';
//var shortreg=registration.slice(-3).toLowerCase()
var position = Cesium.Cartesian3.fromDegrees( 2.111137, 49.451413, 200);
var heading = Cesium.Math.toRadians(0-90);
var pitch = 0;
var roll = 0;
var hpr = new Cesium.HeadingPitchRoll(0, 0, 0);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
var vspeed=0;
var modelpitch=0;
var entity;

function startup(Cesium) {
  
    //Sandcastle_Begin

    //Sandcastle_Begin
    var viewer = new Cesium.Viewer('cesiumContainer', {

                                       timeline : false,
                                       homeButton:false,
                                       navigationHelpButton:false,
                                       navigationInstructionsInitiallyVisible:false,
                                       infoBox : false,
                                       selectionIndicator : false,
                                       shadows : true,
                                       shouldAnimate : true,
                                       terrainProvider: Cesium.createWorldTerrain({requestVertexNormals : true, requestWaterMask: true}),
                                       scene3DOnly: true,
                                       baseLayerPicker: false,
                                       geocoder:false,
                                       animation:false,
                                       fullscreenButton:false
                                   });

viewer.clock.shouldAnimate = true;
var clock= new Cesium.Clock();

        // Create entity:
        viewer.entities.removeAll();
        entity = viewer.entities.add({
                                               position : position,
                                               orientation : orientation,
                                               model : {
                                                   uri : '../../../../Apps/SampleData/models/CesiumAir/Cesium_Air.glb',
                                                   minimumPixelSize : 128,
                                                   maximumScale : 20000
                                               }

                                           });
    
    // Position of the entity:

    function flightradar()
    {

            var date= Date.now();
            var res;
            var url = 'https://data-live.flightradar24.com/zones/fcgi/feed.js?reg=!’+registration;
            var doc= new XMLHttpRequest();
            doc.onreadystatechange = function(){
                if(doc.readyState == XMLHttpRequest.DONE){
                    res=doc.responseText;//JS.data(doc.responseText);
                    //console.log(res);
                    dataAvailable=res.length
                    if(dataAvailable>40)
                    {
                        var res1=res.split("[");
                        var res2=res1[1];
                        var res3=res2.split(",");
                        res4=parseFloat(res3[1]); //latitude_value
                        res5=parseFloat(res3[2]); // longitude_value
                        res6=parseFloat(res3[3]); // track_value
                        res7=parseFloat(res3[4])*0.3048; // altitude_value
                        res8=parseFloat(res3[5]); // speed_value
                        res9=parseInt((parseFloat(res3[15])*0.4*100)/100); // Vspeed_value
                        datatime=parseInt(res3[10]);// timestamp of data recorded

                    }

            }
                };
            doc.open('GET', url, true);
            doc.send();
}

viewer.trackedEntity = entity;
viewer.clock.onTick.addEventListener(function(clock){

        if(Date.now()>=timer+500)
        {
            flightradar();
            timer=Date.now();
            if(datatime>datatimeold)
            {
             position = Cesium.Cartesian3.fromDegrees(res5,res4,res7);
             heading = Cesium.Math.toRadians(res6-90);
             pitch = 0;
             roll = 0;
             hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
             orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
             entity.orientation=orientation;
             entity.position=position;
             datatimeold=datatime
            }
             viewer.trackedEntity = entity;
             entity.viewFrom= new Cesium.Cartesian3(30, 30, 10);
        }
    if(dataAvailable<40)
{
cameraAngle=0.5;
}else{
cameraAngle=1;
}
    var camera = viewer.camera;
viewer.clock.onTick.addEventListener(function(clock){
if(Date.now()>=camtime+50)
{
        if(track!=='undefined')
        {
            if(camera.heading<Cesium.Math.toRadians(res6))
            {camera.rotateLeft(Cesium.Math.toRadians(cameraAngle));}
            if(camera.heading>Cesium.Math.toRadians(res6))
            {camera.rotateRight(Cesium.Math.toRadians(cameraAngle));}
            track_old=Cesium.Math.toRadians(camera.heading);
            camtime=Date.now();
        }
    }
});
    
       });

    //Sandcastle_End
    Sandcastle.finishedLoading();
};

if (typeof Cesium !== 'undefined') {
    startup(Cesium);
} else if (typeof require === 'function') {
    require(['Cesium'], startup);
};

I see your problem now. Thanks for providing a full code example.

I’m surprised this hasn’t been suggested already but it looks like Cesium’s SampledProperties already have an extrapolation mechanism built in to handle exactly this case, when your data updates frequently but you need to predict where the plane should be before the next update so you get a smooth path:

https://cesiumjs.org/Cesium/Build/Documentation/SampledPositionProperty.html?classFilter=SampledP#forwardExtrapolationDuration

Here’s an example I found from searching through the forum:

https://groups.google.com/d/msg/cesium-dev/Jgpcnkkl600/V8HUOcAoBgAJ

I also made a note to write a tutorial about this, since I think it’s a pretty common use case and it would be nice to have a guide on how to do it in CesiumJS.

Hello Omar,

extrapolation with time, I try that before with my own calculation with this code:

function anticipate ()
{
        // estimated position following speed & heading until server give position

        if(res8<30)
        {
            vit=0
        }else{
            var vit=res8;
        }

        track=res6;

            var lat=res4;
            var lon=res5;

        var radius = 6370640;
        var speed_ms=vit*0.51444;
        var distance=speed_ms*0.06;
        var δ = Number(distance) / radius; // angular distance in radians
        var θ = Number(track)*Math.PI/180;

        var φ1 = lat*Math.PI/180;
        var λ1 = lon*Math.PI/180;

        var sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1);
        var sinδ = Math.sin(δ), cosδ = Math.cos(δ);
        var sinθ = Math.sin(θ), cosθ = Math.cos(θ);

        var sinφ2 = sinφ1*cosδ + cosφ1*sinδ*cosθ;
        var φ2 = Math.asin(sinφ2);
        var y = sinθ * sinδ * cosφ1;
        var x = cosδ - sinφ1 * sinφ2;
        var λ2 = λ1 + Math.atan2(y, x);

        newLat =φ2*180/Math.PI;
        newLong=(λ2*180/Math.PI+540)%360-180;
        var height=res7
        res4=newLat;
        res5=newLong;

        //smooth pitch according VS
        if(modelpitch<res9)
        {
            modelpitch=modelpitch+2
        }
        if(modelpitch>res9)
        {
            modelpitch=modelpitch-2
        }

        // final position of the entity

        position = Cesium.Cartesian3.fromDegrees(res5,res4,height);
        heading = Cesium.Math.toRadians(res6-90);
        pitch = Cesium.Math.toRadians(modelpitch/100);
        roll = 0;
        hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
        orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
        datatimeold=datatime;
    };

it's working fine but after update, I got small jump fwd or bwd because the received data are not exactly at the same interval...

That why my idea was to put few data in buffer and use same method than the one used

but replacing the part where the the position data are created:

function computeCirclularFlight(lon, lat, radius) {
    var property = new Cesium.SampledPositionProperty();
    for (var i = 0; i <= 360; i += 45) {
        var radians = Cesium.Math.toRadians(i);
        var time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
        var position = Cesium.Cartesian3.fromDegrees(lon + (radius * 1.5 * Math.cos(radians)), lat + (radius * Math.sin(radians)), Cesium.Math.nextRandomNumber() * 500 + 1750);
        property.addSample(time, position);

        //Also create a point for each sample we generate.
        viewer.entities.add({
            position : position,
            point : {
                pixelSize : 8,
                color : Cesium.Color.TRANSPARENT,
                outlineColor : Cesium.Color.YELLOW,
                outlineWidth : 3
            }
        });
    }
    return property;
}

with the data from a buffer and then, move the model from one point to an other.

I thrust property.addSample(time, position) does this job, creat buffer but It didn't do what I expect, then, I don't know how to do that...

Hello,

At this time, I success to use new Cesium.SampledPositionProperty(); to get smooth moving of my model.

But as soon I'm trying to use orientation: new Cesium.VelocityOrientationProperty(position)

I receive error:
Unable to get property 'addEventListener' of undefined or null reference (on line 97 of https://cesiumjs.org/Cesium/Source/DataSources/VelocityVectorProperty.js)

do you know why or what I did wrong?...

var newLat=49.451413;
var newLong=2.111137;
var track_old=0;
var delta_track=0;
var track=0;
var res4=49.451413; //latitude_value
var res5=2.111137; // longitude_value
var res6=0;
var res7=150;
var res8=0;
var res9=0;
var timer=0;
var camtime=Date.now();
var datatime=0;
var datatimeold=0;
var cameraAngle = 1;
var dataAvailable=0;
var position= Cesium.Cartesian3.fromDegrees( 2.111137, 49.451413, 200);
var timerimmat= 0;
var registration= 'OE-ICU';
var start=Date.now();
var stop = Cesium.JulianDate.addSeconds(start, 360, new Cesium.JulianDate());
var time = Cesium.JulianDate.addSeconds(start, 45, new Cesium.JulianDate());
var timermodel=0;
var i=0;
var vspeed=0;

var viewer = new Cesium.Viewer('cesiumContainer', {
    infoBox: false, //Disable InfoBox widget
    selectionIndicator: false, //Disable selection indicator
    shouldAnimate: true, // Enable animations
    terrainProvider: Cesium.createWorldTerrain()
});

//Enable lighting based on sun/moon positions
viewer.scene.globe.enableLighting = true;

//Enable depth testing so things behind the terrain disappear.
viewer.scene.globe.depthTestAgainstTerrain = true;

//Set the random number seed for consistent results.
Cesium.Math.setRandomNumberSeed(3);
flightradar();

function clocking(){
stop = Cesium.JulianDate.addHours(datatime,12,new Cesium.JulianDate());
time = datatime;//Cesium.JulianDate.addSeconds(start, 45, new Cesium.JulianDate());
//Make sure viewer is at the desired time.
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
//viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; //Loop at the end
viewer.clock.multiplier = 1;
    //Set timeline to simulation bounds
viewer.timeline.zoomTo(start, stop);
}

//Generate a random circular pattern with varying heights.
var property = new Cesium.SampledPositionProperty();

function computeCirclularFlight(lon, lat, radius, height, timedata) {

        //time = timedata;//;//timedata;
        position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
        property.addSample(time, position);

        //Also create a point for each sample we generate.
        viewer.entities.add({
            position : position,
            point : {
                pixelSize : 8,
                color : Cesium.Color.TRANSPARENT,
                outlineColor : Cesium.Color.YELLOW,
                outlineWidth : 3
            }
        });
    
    return property;
}

//Actually create the entity
var entity = viewer.entities.add({

    //Use our computed positions
    position : position,

    //Automatically compute orientation based on position movement.
   // orientation : new Cesium.VelocityOrientationProperty(position),

    //Load the Cesium plane model to represent the entity
    model : {
        uri : '../../SampleData/models/CesiumAir/Cesium_Air.gltf',
        minimumPixelSize : 64
    }
});

viewer.trackedEntity = entity;

    function flightradar()
    {

            var date= Date.now();
            var res;
            var url = 'https://data-live.flightradar24.com/zones/fcgi/feed.js?reg=!’+registration;
            var doc= new XMLHttpRequest();
            doc.onreadystatechange = function(){
                if(doc.readyState == XMLHttpRequest.DONE){
                    res=doc.responseText;//JS.data(doc.responseText);
                    //console.log(res);
                    dataAvailable=res.length
                    if(dataAvailable>40)
                    {
                        var res1=res.split("[");
                        var res2=res1[1];
                        var res3=res2.split(",");
                        res4=parseFloat(res3[1]); //latitude_value
                        res5=parseFloat(res3[2]); // longitude_value
                        res6=parseFloat(res3[3]); // track_value
                        res7=parseFloat(res3[4])*0.3048; // altitude_value
                        res8=parseFloat(res3[5]); // speed_value
                        res9=parseInt((parseFloat(res3[15])*0.4*100)/100); // Vspeed_value
                        datatime=Cesium.JulianDate.fromDate(new Date(parseInt(res3[10])*1000));// timestamp of data recorded
                        if(datatime!==undefined && datatime!==0 && datatimeold<parseInt(res3[10]))
                           {
                           start=datatime;
                           datatimeold=parseInt(res3[10]);
                           
                           }
                    }

            }
                };
            doc.open('GET', url, true);
            doc.send();
}
viewer.clock.onTick.addEventListener(function(clock){
        if(Date.now()>=timer+500)
        {
            flightradar();
            if(datatime!==undefined && datatime!==0)
            {
             
             clocking();
             computeCirclularFlight(res5,res4,0.03,res7,datatime);
             
             datatimeold=datatime;
                //i=0;
            }
             //viewer.trackedEntity = entity;
            entity.viewFrom= new Cesium.Cartesian3(30, 30, 10);
            timer=Date.now();
        }
                if(Date.now()>=timermodel+10)
        {
          
            if(Date.now()<=timer+500)
        {
            
            if(datatime!==undefined && datatime!==0)
            {
            i=i+0.01;
            entity.position=property.getValue(Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate()));
            timermodel=Date.now();
            }
        }
            
        }
    
});

I think you need to provide the SampledPositionProperty to your VelocityOrientationProperty. So instead of:

orientation : new Cesium.VelocityOrientationProperty(position)

``

It would be:

orientation : new Cesium.VelocityOrientationProperty(property)

``

oh great it's working!!

You are my hero Omar!!!

oups no, the model turn to 180 to wrong direction after few seconds...

after some test, the heading of the airplane do not change, it stay to the north

Discussion continued here: https://groups.google.com/d/msg/cesium-dev/2LBVPoPQypY/WykY3HRjCAAJ