How God's Eye View Uses satellite.js for Real-Time Orbit Rendering
God's Eye View leverages satellite.js to parse CelesTrak Two-Line Elements (TLE) into SGP4 propagation records, calculating real-time Earth-Centered Inertial (ECI) positions that are transformed into geodetic coordinates for Cesium rendering.
The open-source project bilawalsidhu/gods-eye-view visualizes satellite constellations in a web-based 3D globe by integrating the satellite.js library with CesiumJS. The implementation combines SGP4 orbital mechanics with a baked-geometry technique to render smooth, flicker-free orbit rings that stay synchronized with Earth's rotation.
Parsing TLEs and Initializing SGP4 Records
The pipeline begins in src/data/satellites.js by converting raw TLE strings into propagator objects. The twoline2satrec function parses the two-line element sets into an SGP4 record (satrec) that contains the orbital elements required for propagation.
// src/data/satellites.js – dense catalog load
import { twoline2satrec } from 'satellite.js';
const satrec = twoline2satrec(entry.line1, entry.line2);
This initialization occurs once during data ingestion, typically while loading dense catalogs like the Starlink constellation. The resulting satrec object persists in memory and serves as the input for all subsequent position calculations.
Real-Time Position Propagation
For every render frame, the application calculates the satellite's current latitude, longitude, and altitude using the propagatePosition function. This method chains several satellite.js APIs to convert from ECI coordinates to geodetic values.
// src/data/satellites.js – propagatePosition()
function propagatePosition(satrec, date) {
const posVel = propagate(satrec, date); // SGP4 propagation step
const gmst = gstime(date); // Greenwich Mean Sidereal Time
const geo = eciToGeodetic(posVel.position, gmst); // ECI → Geodetic conversion
return {
longitude: degreesLong(geo.longitude),
latitude: degreesLat(geo.latitude),
altitude: geo.height * 1000,
};
}
The propagate function returns an ECI position vector valid for the specified date. The gstime calculation provides the Earth rotation angle required to align the inertial frame with the Earth-fixed frame, while eciToGeodetic performs the coordinate transformation. Helper functions degreesLong and degreesLat normalize the radian outputs to degrees for Cesium consumption.
Baking Static Orbit Geometry
To eliminate per-frame geometry regeneration, the system pre-computes a full orbital ring using computeOrbitPath. This function samples the satellite's position at regular intervals across one orbital period, locking the GMST to a fixed reference date to ensure the polyline closes perfectly.
// src/data/satellites.js – computeOrbitPath()
function computeOrbitPath(satrec, referenceDate) {
const periodSec = orbitalPeriodSeconds(satrec);
const stepSec = periodSec / ORBIT_PATH_STEPS; // 180 steps by default
const fixedGmst = gstime(referenceDate); // Lock GMST for the entire ring
for (let i = 0; i <= ORBIT_PATH_STEPS; i++) {
const t = new Date(referenceDate.getTime() + i * stepSec * 1000);
const posVel = propagate(satrec, t);
const geo = eciToGeodetic(posVel.position, fixedGmst);
positions.push(Cesium.Cartesian3.fromDegrees(
degreesLong(geo.longitude),
degreesLat(geo.latitude),
geo.height * 1000
));
}
return positions;
}
By holding fixedGmst constant during the sampling loop, the resulting positions represent the orbit in a rotating reference frame attached to the Earth. This creates a closed, static polyline that accurately depicts the ground track without requiring dynamic vertex updates.
Synchronizing Orbits with Earth's Rotation
The baked orbit ring remains visually accurate through a model matrix transformation rather than geometry reconstruction. The _showOrbitPath method creates a Cesium Primitive with the pre-calculated positions, storing the GMST value used during baking.
// src/data/satellites.js – _showOrbitPath()
function _showOrbitPath(noradId, color) {
const bakeDate = new Date();
const basePositions = computeOrbitPath(satrec, bakeDate);
const primitive = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolylineGeometry({
positions: basePositions,
width: noradId === ISS_NORAD ? 2.5 : 2.0,
vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT,
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color.withAlpha(0.6)),
depthFailColor: Cesium.ColorGeometryInstanceAttribute.fromColor(color.withAlpha(0.35)),
},
}),
appearance: new Cesium.PolylineColorAppearance({ translucent: true }),
depthFailAppearance: new Cesium.PolylineColorAppearance({ translucent: true }),
asynchronous: false,
allowPicking: false,
});
_viewer.scene.primitives.add(primitive);
_orbitPaths.set(noradId, { primitive, gmstAtBake: gstime(bakeDate) });
}
Each second, the _updateOrbitPathRotations function calculates the rotation delta between the bake-time GMST and the current GMST, applying this as a model matrix transformation to the primitive. This approach eliminates the flickering artifacts associated with regenerating Entity-based polylines every tick while maintaining sub-second accuracy.
// src/data/satellites.js – _updateOrbitPathRotations()
function _updateOrbitPathRotations(nowDate) {
for (const path of _orbitPaths.values()) {
orbitFrameModelMatrix(path.gmstAtBake, nowDate, path.primitive.modelMatrix);
}
}
The orbitFrameModelMatrix utility (lines 441-452 in src/data/satellites.js) constructs a rotation matrix that accounts for the Earth's rotation since the geometry was baked, keeping the orbit ring aligned with the satellite's actual ground track.
Complete Implementation Example
The following snippet demonstrates the complete workflow for tracking a satellite manually, mirroring the production implementation:
import { twoline2satrec, propagate, gstime, eciToGeodetic, degreesLong, degreesLat } from 'satellite.js';
import * as Cesium from 'cesium';
// 1. Parse TLE (ISS example)
const tleLine1 = '1 25544U 98067A 24278.55555556 .00001234 00000-0 29233-4 0 9991';
const tleLine2 = '2 25544 51.6430 12.3456 0001234 23.4567 45.6789 15.48812345678901';
const satrec = twoline2satrec(tleLine1, tleLine2);
// 2. Propagate to current time
const now = new Date();
const { position } = propagate(satrec, now);
const gmst = gstime(now);
const geo = eciToGeodetic(position, gmst);
// 3. Render current position
const viewer = new Cesium.Viewer('cesiumContainer');
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
degreesLong(geo.longitude),
degreesLat(geo.latitude),
geo.height * 1000
),
point: { pixelSize: 8, color: Cesium.Color.YELLOW }
});
To render the full orbital ring, sample positions across one period using computeOrbitPath, then attach a pre-render callback to maintain synchronization via orbitFrameModelMatrix.
Summary
- Data Ingestion: Uses
twoline2satrecinsrc/data/satellites.jsto parse TLEs into SGP4 records during catalog loading. - Coordinate Transformation: Chains
propagate,gstime, andeciToGeodeticto convert ECI vectors to geodetic coordinates for Cesium. - Geometry Baking: Pre-computes orbit polylines with a fixed GMST reference to create closed, static geometry rings.
- Rotation Synchronization: Updates orbit alignment via model matrix transformations rather than geometry regeneration, eliminating render flicker.
- Integration: Combines satellite.js propagation with Cesium
Primitiveobjects for high-performance visualization of thousands of satellites.
Frequently Asked Questions
Why does God's Eye View bake orbit geometry instead of regenerating it every frame?
Baking the orbit path once with a fixed GMST creates a closed, static polyline that represents the satellite's ground track relative to the rotating Earth. By applying a rotation matrix to the primitive each second via _updateOrbitPathRotations, the system avoids the CPU overhead and visual flickering associated with recalculating hundreds of vertices per satellite every frame. This technique enables smooth 60fps rendering even with large constellations like Starlink.
What is the role of GMST in satellite.js orbit rendering?
Greenwich Mean Sidereal Time (GMST) represents the rotation angle of the Earth relative to the inertial frame. In src/data/satellites.js, gstime calculates this angle for a given date, enabling the transformation of ECI coordinates (output by SGP4 propagation) into Earth-fixed geodetic coordinates. For orbit baking, locking the GMST ensures the polyline represents a consistent ground track, while the model matrix rotation accounts for the time elapsed since baking.
How accurate is the SGP4 propagation used in God's Eye View?
The satellite.js implementation provides standard SGP4 propagation accuracy, typically within a few kilometers for Low Earth Orbit satellites near the epoch of the TLE data. The visualization accuracy depends on the freshness of the TLE data in the CelesTrak catalog. The code does not apply additional perturbation corrections beyond the standard SGP4 model implemented in the library.
Can this approach work with 3D libraries other than Cesium?
Yes. While src/data/satellites.js uses Cesium Primitive objects and Cartesian3 coordinates, the core satellite.js functions (twoline2satrec, propagate, eciToGeodetic) return standard JavaScript numbers and objects. The baked orbit positions are simple longitude/latitude/altitude arrays that can feed into Three.js, Babylon.js, or custom WebGL renderers by adapting the coordinate conversion and matrix math to the target framework's conventions.
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 →