5

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

Florent Du
  • 71
  • 1
  • 4
  • Cesium includes a couple functions for converting Euler Angles to quaternions, as well as convert from local-up axes to Earth-fixed. Maybe you could write a NodeJS script to batch-convert using these functions? – emackey Apr 13 '17 at 13:38
  • thx @emackey So you mean that I could write a NodeJS script to convert pitch heading roll + position into quaternion using Cesium functions ? – Florent Du Apr 13 '17 at 14:01
  • I think that should be possible, yes. I haven't tried doing it myself. Position stays separate from heading/pitch/roll. – emackey Apr 13 '17 at 14:02

4 Answers4

3

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!

emackey
  • 9,924
  • 2
  • 25
  • 48
  • Thank you for your answer. I will try to use cesium instead of czml to set my orientation. However, I don't see how to set orientation properly in cesium. For positions it is easy I used Cesium.SampledPositionProperty() and I gave positions in function of time. How can I give orientations in function of time as I do with positions ? In the example below, how can I replace the function new Cesium.VelocityOrientationProperty(position) by my own orientation values in function of time ? https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Interpolation.html&label=Showcases – Florent Du Apr 25 '17 at 13:48
  • I just implemented this and it seems to work great. [My TypeScript code](https://gist.github.com/thw0rted/4857eea152fcc54265330c00775f0fb5), if it helps. – Coderer May 04 '18 at 12:25
  • Looks like getRotation is no longer part of cesium Matrix4. Alternatives? – FredArters Oct 12 '20 at 15:13
  • Looks like getRotation got [renamed to getMatrix3 in 1.65](https://github.com/CesiumGS/cesium/blob/master/CHANGES.md#1650---2020-01-06). I'll update the answer, thanks. – emackey Oct 13 '20 at 00:07
  • Thanks for the update! Is the goal here to convert the lat, lon, alt and HPR to cesium coordinates for placing an object in a scene? I stumbled across this question trying to figure out how translate a camera attached to an aircraft where the aircraft HPR is already North East Down and I want to scale (rotate?) the aircraft HPR by the camera's HPR (as it's relative to the aircraft) giving me an accurate look at HPR in earth NED values. I'm converting both HPR to quaternions, multiplying them and getting the HPR back out. Gets close but something is just off dep on.. unsure what. Thoughts? – FredArters Oct 14 '20 at 01:26
  • Just read up... looks like eastNorthUpToFixedFrame is being used to transform between ENU and ECEF coords. I think I understand now (haha) and *think I am taking the right approach for what I am doing maybe. – FredArters Oct 14 '20 at 05:54
  • Ok. If you run into more trouble, you could post a new separate question on it. – emackey Oct 15 '20 at 13:09
2

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'); 
Florent Du
  • 71
  • 1
  • 4
0

The solution seems to be to use sampledProperty with quaternions. There are explanations here : https://groups.google.com/forum/#!topic/cesium-dev/gb4HdaTTgXE

I didn't try it yet but I will post the solution when I will succeed.

Florent Du
  • 71
  • 1
  • 4
0

I advice this function Cesium.Transforms.headingPitchRollQuaternion(). The parameters are about the local hpr rather than the quaternion in the reference of Earth fixed axes. Then you don't need to do the change.

I guess you use this one: Cesium.Quaternion.fromHeadingPitchRoll. Hope this may help.

TT.
  • 14,883
  • 6
  • 41
  • 77