Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CESIUM : How to animate an aircraft from pitch, roll, heading?

I have flight data containing position (lat,lon,height) and orientation (pitch, roll, heading) in function of time.

I would like to represent the aircraft in CesiumJS.

I tried to create CZML file from the flight data. Everything worked fine for the position. But CZML format support only orientation based on a quaternion in the reference of Earth fixed axes. That means I would have to preprocess this quaternion for each position, pitch, roll, heading in order to write the CZML.

Do you think I should implement this quaternion computation (not straight forward) ?

Or should I use another solution to use cesium functions that allow me to use directly pitch, roll, heading values ?. In this case, I wonder which format I can use to transfer my flight data to Cesium.

Thank you for your advices

like image 797
Florent Du Avatar asked Apr 13 '17 09:04

Florent Du


2 Answers

I found how to orientate a model based on pitch, roll, heading values in function of time. It is a pity that there is no documentation on SampledProperty used with Cesium.Quaternion in the official website. Also there is no example with in the sandCastle.

I modified a few lines of a code example to illustrate orientation possibilities in cesium (copy/paste the code in HTML and JAVASCRIPT tabs in Cesium sandCastle. Only the heading takes different values in this example but you can play with pitch and roll too.

HTML CODE :

<style> 
    @import url(../templates/bucket.css); 
</style> 
<div id="cesiumContainer" class="fullSize"></div> 
<div id="loadingOverlay"><h1>Loading...</h1></div> 
<div id="toolbar"> 
    <div id="interpolationMenu"></div> 
</div> 

JAVASCRIPT CODE :

var viewer = new Cesium.Viewer('cesiumContainer', { 
    terrainProviderViewModels : [], //Disable terrain changing 
    infoBox : false, //Disable InfoBox widget 
    selectionIndicator : false //Disable selection indicator 
}); 

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

//Use STK World Terrain 
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({ 
    url : 'https://assets.agi.com/stk-terrain/world', 
    requestWaterMask : true, 
    requestVertexNormals : 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); 

//Set bounds of our simulation time 
var start = Cesium.JulianDate.fromDate(new Date(2015, 2, 25, 16)); 
var stop = Cesium.JulianDate.addSeconds(start, 360, 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 = 10; 

//Set timeline to simulation bounds 
viewer.timeline.zoomTo(start, stop); 




var lon = 0; 
var lat = 45; 
var radius = 0.03; 

//Generate a random circular pattern with varying heights. 

    var positionProperty = new Cesium.SampledPositionProperty(); 
    var orientationProperty = new Cesium.SampledProperty(Cesium.Quaternion); 

    for (var i = 0; i <= 360; i += 45) { 
        var radians = Cesium.Math.toRadians(i); 
        var time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate()); 

        // compute positions 
        var position = Cesium.Cartesian3.fromDegrees(lon + (radius * 1.5 * Math.cos(radians)), lat + (radius * Math.sin(radians)), Cesium.Math.nextRandomNumber() * 500 + 1750); 
        positionProperty.addSample(time, position); 

        // compute orientations 
        var heading = Cesium.Math.toRadians(90+i); 
        var pitch = Cesium.Math.toRadians(20); 
        var roll = Cesium.Math.toRadians(0);       
        var hpRoll = new Cesium.HeadingPitchRoll(heading,pitch,roll);   
        var orientation = Cesium.Transforms.headingPitchRollQuaternion(position,hpRoll); 
        orientationProperty.addSample(time, orientation); 

        //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 
            } 
        }); 
    } 


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

    //Set the entity availability to the same interval as the simulation time. 
    availability : new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({ 
        start : start, 
        stop : stop 
    })]), 

    //Use our computed positions 
    position : positionProperty, 

    //Automatically compute orientation based on position movement. 
    orientation : orientationProperty, 

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

    //Show the path as a pink line sampled in 1 second increments. 
    path : { 
        resolution : 1, 
        material : new Cesium.PolylineGlowMaterialProperty({ 
            glowPower : 0.1, 
            color : Cesium.Color.YELLOW 
        }), 
        width : 10 
    } 
}); 

//Add button to view the path from the top down 
Sandcastle.addDefaultToolbarButton('View Top Down', function() { 
    viewer.trackedEntity = undefined; 
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90))); 
}); 

//Add button to view the path from the side 
Sandcastle.addToolbarButton('View Side', function() { 
    viewer.trackedEntity = undefined; 
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(-90), Cesium.Math.toRadians(-15), 7500)); 
}); 

//Add button to track the entity as it moves 
Sandcastle.addToolbarButton('View Aircraft', function() { 
    viewer.trackedEntity = entity; 
}); 

//Add a combo box for selecting each interpolation mode. 
Sandcastle.addToolbarMenu([{ 
    text : 'Interpolation: Linear Approximation', 
    onselect : function() { 
        entity.position.setInterpolationOptions({ 
            interpolationDegree : 1, 
            interpolationAlgorithm : Cesium.LinearApproximation 
        }); 
    } 
}, { 
    text : 'Interpolation: Lagrange Polynomial Approximation', 
    onselect : function() { 
        entity.position.setInterpolationOptions({ 
            interpolationDegree : 5, 
            interpolationAlgorithm : Cesium.LagrangePolynomialApproximation 
        }); 
    } 
}, { 
    text : 'Interpolation: Hermite Polynomial Approximation', 
    onselect : function() { 
        entity.position.setInterpolationOptions({ 
            interpolationDegree : 2, 
            interpolationAlgorithm : Cesium.HermitePolynomialApproximation 
        }); 
    } 
}], 'interpolationMenu'); 
like image 61
Florent Du Avatar answered Oct 17 '22 02:10

Florent Du


I haven't tried this myself, but it should be possible to convert an aircraft heading/pitch/roll (in local axis of known aircraft position) into an Earth-fixed Quaternion, using only math functions that ship with Cesium.

You have two transformations needed here, one is the heading-pitch-roll to quaternion, the other is local axes to Earth-fixed.

  1. Convert simple heading-pitch-roll to Quaternion, that's done with Quaternion.fromHeadingPitchRoll. This was the easy part and it's done. Save this result for later.

Now we need local to Earth-fixed.

  1. Use Transforms.eastNorthUpToFixedFrame. This takes position into account, but gives you more than you need in the form of a Matrix4.

  2. Get just the rotation from your Matrix4 using Matrix4.getMatrix3. This strips off the transform offset (Earth-to-aircraft) and yields a Matrix3 containing only the rotational offset (and scale, but that should just be the identity scale given where we obtained this matrix in the previous step, so we can safely think of this as only the rotation).

  3. Use Quaternion.fromRotationMatrix to convert your Matrix3 into a Quaternion.

Now you have 2 quaternions, one from step 1 and another from step 4.

  1. Multiply the two quaternions together using Quaternion.multiply. The result here should be the answer you need.

I hope my math is right. Good luck!

like image 35
emackey Avatar answered Oct 17 '22 02:10

emackey