Heading / pitch / roll


Following on from this question: https://groups.google.com/forum/m/#!topic/cesium-dev/4j991i8iazQ if I go the JS rather than CZML route the HeadingPitchRoll demo in the Sandcastle is more-or-less what I want to achieve but with the model following a list of lat / long / height positions and heading / pitch / roll attitudes. I've been trying to use timeIntervalCollections to hold the list of data.

Please would someone be kind enough to suggest how I could best achieve this? I am really stuck.

Thank you,

Hi Hugh,

If you have a long list of values that you want to use to use to update a model attribute over time, I would probably use a CallbackProperty. Here’s a simple example that updates a list of positions: https://groups.google.com/d/msg/cesium-dev/4Uj-ZMBcjZM/UNjEk2NxAwAJ

In your case, you could have a list of heading/pitch/roll values and set your model’s orientation property as a callback function that returns the appropriate value from a list (say, incrementing an index into your list of values.

I hope that helps! If this is still confusing, I’d be happy to write up a more specific example.


  • Rachel

TimeIntervalCollection is for values that (as the name implies) that change over an interval. So if you wanted an object to “jump” from place to place, it would work but it’s usually not used with positions in that way. It sounds like you want actual motion.

While a CallbackProperty could work here, if you have pre-computed or dynamically generated time-tagged values, the better solution is SampledPositionProperty for positions and SampledProperty(Quaternion) for orientation.

This example shows SampledPositionProperty in action: http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Interpolation.html&label=Showcases (we actually don’t have an example for HPR orientation, but we really should) You can create quaternions via Quaternion.fromHeadingPitchRoll

CallbackProperty is usually a catch-all for values that need to be computed on the fly or other dynamic data that we don’t have structured Property implementations for.

Thank you Rachel and Matthew,

I think I am nearly there - but not quite…

The code below loads position into a sampled position property and the heading information into hdg (I plan to move this to quaternions).

For testing I’ve given some nominal values to pitch, heading and roll and calculated an orientation from that. As soon as I try to apply it to the entity (via the line marked ‘THIS LINE’) the model disappears. If you could give me the crucial clue to get me over the finish line I would be very grateful.

Thank you


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;

viewer.trackedEntity = entity;

//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 bounds of our simulation time

var start = Cesium.JulianDate.fromIso8601(‘2012-08-04T10:00:10Z’);

var stop = Cesium.JulianDate.fromIso8601(‘2012-08-04T10:03:10Z’);

//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 = 4;

//Set timeline to simulation bounds

viewer.timeline.zoomTo(start, stop);

// Add heading

function storeHeading() {

 var hdg = new Cesium.SampledProperty(Number);


    //Populate it with data

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:34Z'), 88);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:35Z'), 87.6);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:36Z'), 87.6);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:37Z'), 88.3);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:38Z'), 89);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:39Z'), 89.7);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:40Z'), 89.7);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:41Z'), 90.1);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:42Z'), 90.8);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:43Z'), 90.8);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:44Z'), 90.8);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:45Z'), 91.2);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:46Z'), 91.5);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:47Z'), 90.8);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:48Z'), 90.4);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:49Z'), 90.4);

    hdg.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:50Z'), 90.8);

return hdg;


//Generate a random circular pattern with varying heights.

function computeFlight() {

var property = new Cesium.SampledPositionProperty();

    // Populate it with data        

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:35Z'), Cesium.Cartesian3.fromDegrees(-60.9649963, 13.7333345, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:36Z'), Cesium.Cartesian3.fromDegrees(-60.9649353, 13.7333384, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:37Z'), Cesium.Cartesian3.fromDegrees(-60.9648476, 13.7333422, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:38Z'), Cesium.Cartesian3.fromDegrees(-60.9647331, 13.733346, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:39Z'), Cesium.Cartesian3.fromDegrees(-60.9645958, 13.733346, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:40Z'), Cesium.Cartesian3.fromDegrees(-60.9644279, 13.733346, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:41Z'), Cesium.Cartesian3.fromDegrees(-60.9642372, 13.733346, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:42Z'), Cesium.Cartesian3.fromDegrees(-60.9640236, 13.7333441, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:43Z'), Cesium.Cartesian3.fromDegrees(-60.9637871, 13.7333403, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:44Z'), Cesium.Cartesian3.fromDegrees(-60.9635239, 13.7333364, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:45Z'), Cesium.Cartesian3.fromDegrees(-60.9632378, 13.7333317, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:46Z'), Cesium.Cartesian3.fromDegrees(-60.962925, 13.7333241, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:47Z'), Cesium.Cartesian3.fromDegrees(-60.9625931, 13.7333183, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:48Z'), Cesium.Cartesian3.fromDegrees(-60.9622383, 13.7333145, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:49Z'), Cesium.Cartesian3.fromDegrees(-60.9618607, 13.7333107, 21));

    property.addSample(Cesium.JulianDate.fromIso8601('2012-08-04T10:00:50Z'), Cesium.Cartesian3.fromDegrees(-60.9614601, 13.7333069, 21));

return property;


//Compute the entity position property.

var position = computeFlight();

var hdg = storeHeading(0);


var heading = Cesium.Math.toRadians(135);

var pitch = 0;

var roll = 0;

var orientation = new Cesium.HeadingPitchRoll( heading, pitch, roll);

//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 : position,


//compute orientation 

// orientation : orientation, /// THIS LINE

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



I missed the ‘quarternion’ from ‘Cesium.Quaternion.HeadingPitchRoll( heading, pitch, roll)’ but I still can’t get it to work. Any suggestions appreciated!

Hi Hugh,


var orientation = new Cesium.HeadingPitchRoll( heading, pitch, roll);

needs to be

var orientation = Cesium.Quaternion.fromHeadingPitchRoll(heading, pitch, roll);

I don’t see anything else wrong yet, but I’ll take another look later.


  • Rachel

Thanks Rachel,

I think that just the job. In future I’ll use copy-and-paste rather than try and remember things!

Hi Rachel,

I’m struggling to use the sampled property ‘hdg’ in the fromHeadingPitchRoll. The error says the normalised result is not a number.

Hi Hugh,

Do you have an updated code sample? We might be able to take a look. One of the easiest way to share code snippets is to use Sandcastle: http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Hello%20World.html&label=Showcases

Just paste your javascript into the code panel, then press the share button at the top to save your demo and generate a sharable link.


  • Rachel

Hi Rachel.


[Hopefully] the code is here… http://cesiumjs.org/Cesium/Apps/Sandcastle/?src=Hello%20World.html&label=Showcases&gist=1a0bf5af7427dd00280299907ab47491

At line 310 I’d like to use the sampled values of heading in the “Quaternion.fromHeadingPitchRoll” in line 313 (and then eventually, pitch and roll).

(I’m still trying to get my head around converting to local co-ordinates so ignore any of that!)

Any assistance would be very much appreciated.

  • Hugh

Hi Rachel,

As soon as I try to use the sampled parameter ‘hdg’ in the heading/pitch/roll I get the following error:

RangeError: Invalid array length
RangeError: Invalid array length
at updateFrustums (http://localhost:8080/Source/Scene/Scene.js:1270:36)
at createPotentiallyVisibleSet (http://localhost:8080/Source/Scene/Scene.js:1495:13)
at executeCommandsInViewport (http://localhost:8080/Source/Scene/Scene.js:2248:9)
at updateAndExecuteCommands (http://localhost:8080/Source/Scene/Scene.js:2110:17)
at render (http://localhost:8080/Source/Scene/Scene.js:2582:9)
at Scene.render (http://localhost:8080/Source/Scene/Scene.js:2620:13)
at CesiumWidget.render (http://localhost:8080/Source/Widgets/CesiumWidget/CesiumWidget.js:682:25)
at render (http://localhost:8080/Source/Widgets/CesiumWidget/CesiumWidget.js:72:32)

(Line 309 of http://cesiumjs.org/Cesium/Apps/Sandcastle/?src=Hello%20World.html&label=Showcases&gist=afd90058afa6f76bbb79935a5bfcdd38)

Now I’m confused!

Now(Line 309

Hi Hugh,

A much better method than storing individual angle values, is to create a SampledProperty of Quaternions, like this:

hdg.addSample(Cesium.JulianDate.fromIso8601(‘2012-08-04T10:00:02Z’), Cesium.Quaternion.fromHeadingPitchRoll(0.0, 0.0, 0.0));


Then when you create the plane entity, you can just set it’s orientation direction to the SampledProperty(Quaternion), hdg, or whatever else you want to call it. This will be easier, and more efficient.

Hope that helps!

  • Rachel

Thanks, Rachel.

Just what I needed to know!