# How to Display World-Space Headings for Icons on a CesiumJS Globe

> Learn to display world-space headings for icons on a CesiumJS globe. Use screenProjectedRotation and stabilizeScreenRotation for smooth icon orientation and numeric heading labels.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-04

---

**Use `screenProjectedRotation` from [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) to calculate world-space rotation angles for CesiumJS Billboards, apply `stabilizeScreenRotation` to prevent jitter, and attach a `Cesium.Label` to display the numeric heading value.**

Rendering moving entities like aircraft and vessels on a CesiumJS globe requires accurate orientation to reflect real-world headings. The `bilawalsidhu/gods-eye-view` repository demonstrates how to display world-space headings for icons by projecting course vectors into camera-aligned screen space and stabilizing the resulting rotation values across multiple entity layers.

## Projecting Course Vectors to Screen Space

### Calculating Billboard Rotation with `screenProjectedRotation`

The `screenProjectedRotation` function in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) (lines 52-80) transforms an entity's course vector from world space into the camera's local right/up basis. This ensures that when a **Cesium Billboard** rotates to match its heading, the rotation remains visually consistent regardless of camera position or orbital perspective.

The function accepts the Cesium scene instance, entity position, course in degrees, and current rotation to compute a new screen-projected angle. It returns a rotation value in radians that aligns the icon with the entity's true travel direction relative to the viewport.

### Eliminating Jitter with `stabilizeScreenRotation`

Small fluctuations in calculated angles can cause distracting icon flicker as the camera moves. The `stabilizeScreenRotation` function in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) (lines 84-101) applies a threshold-based filter to ignore minute frame-to-frame changes. It compares the previous rotation against the newly calculated value and only updates when the difference exceeds a specific epsilon, producing smooth, stable heading indicators even during rapid camera transitions.

## Applying Rotations to Entity Layers

### Commercial Flight Implementation

In [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) (lines 1394-1400), the code creates billboards for each aircraft and calls `screenProjectedRotation` to determine the proper orientation. The computed rotation is assigned directly to the billboard's `rotation` property, causing the aircraft icon to point along its actual flight path in world space while compensating for the current camera angle.

### Military and Maritime Vessels

The same pattern appears in [`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js) (lines 2003-2005) for military aircraft and [`src/data/aisLiveVessels.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/aisLiveVessels.js) (lines 1254-1256) for AIS vessel tracking. Each layer imports the orientation utilities and applies world-space rotation to maintain visual consistency across different entity types, ensuring that ships and planes always appear oriented according to their true course regardless of the viewer's perspective.

## Displaying Numeric Heading Labels

Beyond visual orientation, you can display the exact heading value using **Cesium Labels**. Attach a `LabelGraphics` instance to your entity and update its `text` property with the course degrees modulo 360 to keep values within the standard 0-359 range.

The label should share the entity's position but use a `pixelOffset` to appear beside the icon rather than overlapping it. Set the `eyeOffset` to `Cartesian3.ZERO` to keep the label anchored to world coordinates while maintaining screen-space readability.

## Complete Integration Example

```javascript
import {
  screenProjectedRotation,
  stabilizeScreenRotation,
} from './data/iconOrientation.js';

function updateEntityHeading(entity, scene, courseDeg) {
  // 1. Compute world-space rotation
  const pos = entity.position.getValue(Cesium.JulianDate.now());
  const rawRotation = screenProjectedRotation(
    scene, 
    pos, 
    courseDeg, 
    entity.billboard.rotation
  );
  
  // 2. Stabilize to prevent jitter
  const smoothRotation = stabilizeScreenRotation(
    entity.billboard.rotation, 
    rawRotation
  );
  entity.billboard.rotation = smoothRotation;
  
  // 3. Update numeric label
  const headingDeg = Math.round(courseDeg) % 360;
  if (!entity.label) {
    entity.label = new Cesium.LabelGraphics({
      text: `${headingDeg}°`,
      font: '12px sans-serif',
      fillColor: Cesium.Color.WHITE,
      style: Cesium.LabelStyle.FILL,
      pixelOffset: new Cesium.Cartesian2(20, 0),
      eyeOffset: new Cesium.Cartesian3(0, 0, 0)
    });
  } else {
    entity.label.text = `${headingDeg}°`;
  }
}

```

Call this function in your per-frame update loop to maintain accurate heading displays as entities move across the globe.

## Summary

- Use `screenProjectedRotation` from [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) (lines 52-80) to project course vectors into camera-aligned screen space and calculate billboard rotation
- Apply `stabilizeScreenRotation` (lines 84-101) to eliminate rotation jitter and ensure smooth visual updates during camera movement
- Assign the calculated rotation to the `rotation` property of **Cesium Billboards** in your entity layers
- Reference implementations exist in [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js), [`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js), and [`src/data/aisLiveVessels.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/aisLiveVessels.js)
- Attach **Cesium Labels** with modulo-360 heading values to display numeric course information alongside oriented icons

## Frequently Asked Questions

### How does `screenProjectedRotation` maintain correct orientation when the camera moves?

The function transforms the entity's course vector into the camera's local right/up coordinate system, ensuring the billboard rotation reflects the world-space heading regardless of camera position. As implemented in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js), this projection accounts for the current view matrix to maintain visual alignment even when orbiting around the entity.

### Why is rotation stabilization necessary for heading indicators?

Without stabilization, minor floating-point variations in projection calculations cause icons to jitter between frames when the camera moves. The `stabilizeScreenRotation` function in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) applies a threshold-based filter that ignores changes below a specific epsilon, producing smooth transitions while preserving significant heading updates that represent actual course changes.

### Can I use this approach for 3D models instead of billboards?

While the repository implements this specifically for **Cesium Billboards**, the rotation calculation logic in `screenProjectedRotation` applies to any orientation property. For 3D models, you would calculate the quaternion representing the heading from the returned rotation value and assign it to the model's `orientation` property rather than the billboard's `rotation` property.

### How do I format the heading label to show both degrees and cardinal directions?

Extend the label update logic to convert decimal degrees to cardinal bearings (N, NE, E, etc.) before setting the `text` property. The modulo operation (`Math.round(courseDeg) % 360`) ensures values stay within 0-359, which you can map to directional strings using a standard bearing conversion table (0° = N, 45° = NE, etc.).