How Satellite Propagation Works in God's Eye View: A Deep Dive into the SGP4 Pipeline
God's Eye View propagates satellite positions in real time using the SGP4 orbital propagator from satellite.js, converting TLE data into geographic coordinates for Cesium 3D rendering.
This open-source visualization tool renders thousands of satellites on an interactive globe by combining classical orbital mechanics with modern web graphics. Understanding its propagation pipeline reveals how raw two-line element sets transform into smoothly animated orbital tracks.
The Core Propagation Architecture
The satellite propagation system lives primarily in src/data/satellites.js. It orchestrates TLE parsing, SGP4 propagation, coordinate transformation, and Cesium rendering into a cohesive pipeline that updates every second.
TLE Parsing and Satrec Creation
Before propagation can begin, orbital data must be converted into a format the SGP4 algorithm understands. The code imports twoline2satrec from satellite.js to transform TLE pairs into satrec objects—compact orbital element records that the propagator consumes.
import { twoline2satrec, propagate, eciToGeodetic, gstime } from 'satellite.js';
// TLE lines from CelesTrak
const tleLine1 = '1 25544U 98067A 23215.52421948 .00014767 00000-0 26602-4 0 9997';
const tleLine2 = '2 25544 51.6416 288.2418 0001478 33.7972 64.2041 15.50995519397152';
// Create satrec once — this stores the orbital elements
const satrec = twoline2satrec(tleLine1, tleLine2);
The application fetches TLEs from CelesTrak in six core groups, with an optional dense Starlink group for high-density scenarios.
SGP4 Propagation: The Heart of the System
Every POSITION_UPDATE_MS (1000ms), the code invokes propagate(satrec, date) to compute the satellite's position and velocity in Earth-Centered Inertial (ECI) coordinates. This implements the SGP4 (Simplified General Perturbations 4) algorithm—the standard for near-Earth orbit prediction.
const POSITION_UPDATE_MS = 1000;
function updateSatellitePosition(satrec, date) {
// SGP4 propagation: returns position (km) and velocity (km/s) in ECI frame
const { position, velocity } = propagate(satrec, date);
// position: { x, y, z } in kilometers
// velocity: { x, y, z } in kilometers per second
return { position, velocity };
}
The SGP4 algorithm accounts for:
- Earth's oblateness (J2, J3, J4 perturbations)
- Atmospheric drag effects
- Gravitational harmonics
These perturbations are encoded in the satrec's initialization parameters from the TLE epoch and BSTAR drag term.
ECI to Geodetic Coordinate Conversion
Raw ECI coordinates cannot be rendered directly on a globe. The pipeline converts them to geodetic latitude, longitude, and altitude using eciToGeodetic, which requires the Greenwich Sidereal Time (GST) to account for Earth's rotation.
import { gstime, eciToGeodetic, degreesLat, degreesLong } from 'satellite.js';
function getGeographicCoordinates(position, date) {
// Greenwich Sidereal Time at this moment
const gmst = gstime(date);
// Convert ECI to geodetic (WGS-84)
const geodetic = eciToGeodetic(position, gmst);
return {
latitude: degreesLat(geodetic.latitude), // radians → degrees
longitude: degreesLong(geodetic.longitude), // radians → degrees
altitude: geodetic.height // kilometers above ellipsoid
};
}
The gstime function calculates Earth's rotational angle relative to the vernal equinox, essential for aligning inertial coordinates with the rotating Earth.
Rendering Pipeline: From Coordinates to Pixels
Once geographic coordinates are computed, the system feeds them to Cesium's entity system for visualization.
Point Rendering for Live Positions
Each satellite becomes a Cesium PointPrimitive or Entity positioned via Cartesian3.fromDegrees:
import * as Cesium from 'cesium';
function createSatelliteEntity(viewer, lon, lat, altitude, satrec) {
return viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(lon, lat, altitude * 1000), // km to meters
point: {
pixelSize: satrec.satnum === '25544' ? 8 : 4, // ISS gets larger point
color: Cesium.Color.YELLOW,
outlineWidth: 1,
outlineColor: Cesium.Color.BLACK
},
label: {
text: satrec.satnum === '25544' ? 'ISS' : undefined,
font: '12px sans-serif',
fillColor: Cesium.Color.WHITE,
pixelOffset: new Cesium.Cartesian2(0, -10)
}
});
}
Orbital Path Generation
The system generates predictive orbital tracks by sampling future positions. The constant ORBIT_PATH_STEPS (180 points) defines the resolution, with each step separated by POSITION_UPDATE_MS:
const ORBIT_PATH_STEPS = 180;
function generateOrbitPath(satrec, startDate) {
const pathPoints = [];
for (let step = 0; step < ORBIT_PATH_STEPS; step++) {
const futureTime = new Date(startDate.getTime() + step * POSITION_UPDATE_MS);
const { position } = propagate(satrec, futureTime);
const gmst = gstime(futureTime);
const geo = eciToGeodetic(position, gmst);
pathPoints.push(Cesium.Cartesian3.fromDegrees(
degreesLong(geo.longitude),
degreesLat(geo.latitude),
geo.height * 1000
));
}
return pathPoints;
}
// Render as polyline
viewer.entities.add({
polyline: {
positions: pathPoints,
width: 1,
material: Cesium.Color.CYAN.withAlpha(0.7),
arcType: Cesium.ArcType.NONE
}
});
This creates a trailing or leading orbital path that visualizes the trajectory without recalculating the full physics at render time.
Ring Rotation and Time Synchronization
To maintain accurate orientation of pre-computed orbit elements, the system synchronizes with Greenwich Sidereal Time via a dedicated timer. The RING_ROTATION_MS interval (1000ms) ensures that orbital rings stay locked to Earth's rotational frame rather than drifting in inertial space.
const RING_ROTATION_MS = 1000;
function updateRingOrientation(satrecs, viewer) {
// Recompute GST-based orientation for all orbital rings
const now = new Date();
const currentGmst = gstime(now);
// Update ring primitive matrices or recompute path geometries
// Implementation depends on Cesium primitive type used
}
This separation of propagation timing (individual satellites) from ring rotation timing (collective orbital orientation) optimizes performance while maintaining physical accuracy.
ISS Special Handling
The International Space Station (NORAD ID 25544) receives enhanced treatment in the propagation pipeline. In src/data/satellites.js, specific logic identifies this satellite by its catalog number:
const ISS_NORAD_ID = '25544';
function isISS(satrec) {
return satrec.satnum === ISS_NORAD_ID;
}
// Enhanced rendering for ISS
if (isISS(satrec)) {
entity.point.pixelSize = 8;
entity.label.text = 'ISS';
entity.path.show = true; // Always show orbital path for ISS
}
The code also calculates upcoming visible passes via src/data/issPass.js, using the same propagation pipeline to determine when the ISS crosses the observer's horizon.
Complete Propagation Example
Here's the integrated flow as implemented in the source:
import {
twoline2satrec,
propagate,
eciToGeodetic,
gstime,
degreesLat,
degreesLong
} from 'satellite.js';
import * as Cesium from 'cesium';
class SatellitePropagator {
constructor(tleLine1, tleLine2) {
this.satrec = twoline2satrec(tleLine1, tleLine2);
this.entity = null;
}
update(viewer, date) {
// SGP4 propagation step
const { position, velocity } = propagate(this.satrec, date);
// Skip if propagation failed (deorbited or epoch too far)
if (!position) return false;
// Coordinate transformation
const gmst = gstime(date);
const geo = eciToGeodetic(position, gmst);
const lon = degreesLong(geo.longitude);
const lat = degreesLat(geo.latitude);
const alt = geo.height;
// Cesium position update
const cartesian = Cesium.Cartesian3.fromDegrees(lon, lat, alt * 1000);
if (!this.entity) {
this.entity = viewer.entities.add({
position: cartesian,
point: { pixelSize: 4, color: Cesium.Color.YELLOW }
});
} else {
this.entity.position = cartesian;
}
return { longitude: lon, latitude: lat, altitude: alt, velocity };
}
generatePath(startDate, steps = 180, stepMs = 1000) {
const points = [];
for (let i = 0; i < steps; i++) {
const t = new Date(startDate.getTime() + i * stepMs);
const { position } = propagate(this.satrec, t);
if (!position) continue;
const gmst = gstime(t);
const geo = eciToGeodetic(position, gmst);
points.push(Cesium.Cartesian3.fromDegrees(
degreesLong(geo.longitude),
degreesLat(geo.latitude),
geo.height * 1000
));
}
return points;
}
}
Key Files and Their Roles
| File | Function | Key Exports |
|---|---|---|
src/data/satellites.js |
Core propagation engine | TLE loading, satrec management, update loops |
src/data/issPass.js |
ISS pass prediction | Horizon crossing calculations using same propagator |
src/ui.js |
Layer integration | Satellite layer toggle, visibility controls |
src/main.js |
Application entry | Cesium viewer initialization, layer registration |
Summary
- SGP4 via satellite.js powers all orbital predictions, converting TLE epochs to ECI coordinates through the
propagate()function - Two-stage coordinate transformation moves from ECI (propagator output) to geodetic (rendering input) using
eciToGeodeticwithgstime - 1000ms update cycle balances real-time responsiveness with computational efficiency for thousands of satellites
- 180-point orbital paths provide predictive visualization without continuous re-propagation
- Special ISS handling demonstrates extensible architecture for targeting specific satellites with enhanced features
Frequently Asked Questions
What algorithm does God's Eye View use for satellite propagation?
The application uses the SGP4 (Simplified General Perturbations 4) algorithm implemented by the satellite.js library. This is the standard propagator for near-Earth orbits, handling Earth's oblateness and atmospheric drag effects encoded in TLE data.
How accurate are the satellite positions shown?
SGP4 provides accuracy typically within 1–5 kilometers for near-Earth orbits when using recent TLE data. Accuracy degrades as TLE epochs age beyond a few days, which is why God's Eye View fetches fresh TLEs from CelesTrak regularly.
Why does the code use both ECI and geodetic coordinates?
ECI (Earth-Centered Inertial) coordinates simplify orbital mechanics calculations because they don't rotate with Earth. Geodetic coordinates (latitude, longitude, altitude) are required for rendering on Cesium's globe, which is fixed to Earth's rotating frame. The gstime function bridges these reference frames.
Can the propagation timestep be changed from 1 second?
Yes—the POSITION_UPDATE_MS constant controls the update interval. Smaller values increase smoothness but multiply computational load; larger values improve performance at the cost of visual stutter. The 1000ms default optimizes for rendering thousands of satellites simultaneously.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →