Using satellite.js Library for TLE Propagation: A Complete Guide from the God’s Eye View Codebase
The God’s Eye View repository uses satellite.js to parse Two-Line Element sets into satrec objects and propagate them through the SGP4 algorithm, converting ECI coordinates to geodetic latitude/longitude for real-time Cesium rendering.
This guide examines how the open-source God’s Eye View web application leverages the satellite.js library for TLE propagation to visualize real-time satellite positions. By analyzing the production implementation in src/data/satellites.js, we demonstrate how to fetch orbital elements from CelesTrak, execute SGP4 propagation, and render accurate satellite tracks using Cesium.js primitives.
How the Repository Implements TLE Propagation
The architecture follows a three-stage pipeline: acquisition, propagation, and rendering. When the satellite layer initializes, the code builds a _catalog Map that associates NORAD IDs with parsed satrec objects. For high-density visualizations like the Starlink constellation, the system enters "dense mode" and constructs additional entries on-the-fly.
The core dependencies imported from satellite.js in src/data/satellites.js (line 2) include:
twoline2satrec– Converts TLE strings into a propagation recordpropagate– Executes the SGP4 algorithm for a given dategstime– Calculates Greenwich Sidereal TimeeciToGeodetic– Transforms Earth-Centered Inertial coordinates to latitude/longitude/altitude
Parsing TLE Data with parseTLE
Before propagation, raw TLE text must be structured into named entries. The parseTLE helper function (lines 58-69) processes CelesTrak responses by grouping three consecutive non-empty lines into objects containing the satellite name, line 1, and line 2.
function parseTLE(text) {
const lines = text.trim().split('\n')
.map(l => l.trim())
.filter(l => l.length > 0);
const result = [];
for (let i = 0; i < lines.length - 2; i += 3) {
const name = lines[i];
const line1 = lines[i + 1];
const line2 = lines[i + 2];
if (line1.startsWith('1 ') && line2.startsWith('2 ')) {
result.push({ name, line1, line2 });
}
}
return result;
}
For dense catalogs, the code iterates these entries and instantiates propagation records:
const entries = parseTLE(text);
for (const entry of entries) {
const satrec = twoline2satrec(entry.line1, entry.line2);
_catalog.set(noradId, { name: entry.name, satrec, group: 'dense' });
}
SGP4 Propagation with propagatePosition
The propagatePosition function (lines 77-99) in src/data/satellites.js handles the coordinate transformation pipeline. It accepts a satrec object and a JavaScript Date, returning geodetic coordinates suitable for Cesium rendering.
function propagatePosition(satrec, date) {
const posVel = propagate(satrec, date); // SGP4 algorithm
const gmst = gstime(date); // Sidereal time
const geo = eciToGeodetic(posVel.position, gmst);
return {
longitude: degreesLong(geo.longitude),
latitude: degreesLat(geo.latitude),
altitude: geo.height * 1000, // Convert km to meters
speedMps: Math.hypot(posVel.velocity.x,
posVel.velocity.y,
posVel.velocity.z) * 1000
};
}
The system propagates approximately 840 core catalog satellites every second via _propagateAll (lines 3132-3148). For dense Starlink extras, the workload is distributed across frames using _propagateDenseChunk (lines 6060-6075) to maintain 60fps performance.
Rendering Orbital Paths
To visualize complete orbits, the repository pre-computes static paths using computeOrbitPath (lines 111-136). This function samples 180 positions evenly distributed across the orbital period, storing them as a Cesium Primitive.
function computeOrbitPath(satrec, referenceDate) {
const ORBIT_PATH_STEPS = 180;
const periodSec = 86400 / Math.max(satrec.no * (1440 / (2 * Math.PI)), 0.1);
const stepSec = periodSec / ORBIT_PATH_STEPS;
const fixedGmst = gstime(referenceDate);
const baseTime = referenceDate.getTime();
const positions = [];
for (let i = 0; i <= ORBIT_PATH_STEPS; i++) {
const t = new Date(baseTime + i * stepSec * 1000);
const { position } = propagate(satrec, t);
const geo = eciToGeodetic(position, fixedGmst);
positions.push(Cesium.Cartesian3.fromDegrees(
degreesLong(geo.longitude),
degreesLat(geo.latitude),
geo.height * 1000
));
}
return positions;
}
Because the Earth rotates beneath the orbit, the primitive's model matrix updates each second via orbitFrameModelMatrix (lines 441-453), ensuring the ring remains aligned with the satellite's current position.
Tracking Selected Satellites
When a user selects a specific satellite, the application switches from batch point primitives to a tracked Cesium Entity. The position is driven by a CallbackProperty that queries _getTrackedFramePosition (lines 735-779), re-using cached propagation results from the most recent frame update. This architecture avoids redundant SGP4 calculations during camera follow operations while maintaining sub-meter precision.
Handling Edge Cases and Failover
The production implementation includes robustness mechanisms for unreliable network conditions:
- Failed dense loads: If the Starlink TLE feed returns no usable entries,
_denseLoadFailed(lines 1185-1192) automatically reverts to the core catalog to prevent UI degradation. - ISS special casing: The International Space Station receives a larger point primitive, persistent label visibility, and automatic orbit ring display (lines 48-55, 1044-1062), unless it is the currently tracked subject.
- Docked companion grouping: Satellites sharing physical docking locations (like Dragon cargo vehicles at the ISS) are grouped to prevent label clutter (lines 515-556).
These safeguards ensure continuous visualization even when CelesTrak services experience outages.
Standalone Implementation Examples
You can reuse this propagation logic outside the full Cesium application. Below are minimal, runnable implementations of the core functions.
Parse Raw TLE Strings
import { twoline2satrec, propagate, gstime, eciToGeodetic,
degreesLong, degreesLat } from 'satellite.js';
function parseTLE(text) {
const lines = text.trim().split('\n')
.map(l => l.trim())
.filter(l => l.length > 0);
const result = [];
for (let i = 0; i < lines.length - 2; i += 3) {
const name = lines[i];
const line1 = lines[i + 1];
const line2 = lines[i + 2];
if (line1.startsWith('1 ') && line2.startsWith('2 ')) {
result.push({ name, line1, line2 });
}
}
return result;
}
Propagate to Geodetic Coordinates
function propagatePosition(tle, date = new Date()) {
const satrec = twoline2satrec(tle.line1, tle.line2);
const { position, velocity } = propagate(satrec, date);
const gmst = gstime(date);
const geo = eciToGeodetic(position, gmst);
return {
longitude: degreesLong(geo.longitude),
latitude: degreesLat(geo.latitude),
altitude: geo.height * 1000,
speedMps: Math.hypot(velocity.x, velocity.y, velocity.z) * 1000
};
}
// Usage example:
const rawTLE = `ISS (ZARYA)
1 25544U 98067A 24272.53850694 .00016717 00000+0 30641-3 0 9997
2 25544 51.6399 88.8428 0002715 250.3022 109.7395 15.50631073444290`;
const [tle] = parseTLE(rawTLE);
console.log(propagatePosition(tle));
Summary
parseTLE(lines 58-69) transforms raw CelesTrak text into structured entries ready for satellite.js consumption.twoline2satrecandpropagateexecute the SGP4 algorithm to generate ECI position/velocity vectors.eciToGeodeticconverts inertial coordinates to latitude/longitude/altitude using Greenwich Sidereal Time._propagateAllhandles 840+ satellites per second, while_propagateDenseChunkdistributes Starlink calculations across frames.- Orbit rings pre-compute 180 samples per satellite, with
orbitFrameModelMatrixcompensating for Earth rotation. - Failover logic automatically degrades to core catalogs when dense TLE feeds fail, ensuring UI stability.
Frequently Asked Questions
What is TLE propagation and why does God’s Eye View use satellite.js?
TLE propagation is the process of calculating a satellite's position and velocity at a specific time using Two-Line Element sets and perturbation models like SGP4. The God’s Eye View repository uses satellite.js because it provides a battle-tested, JavaScript-native implementation of the SGP4 algorithm that converts TLE strings into ECI coordinates and subsequently to geodetic latitude/longitude, eliminating the need for server-side orbital mechanics calculations.
How does the repository handle failed satellite data loads?
When the Starlink "dense mode" TLE fetch fails, the _denseLoadFailed flag (lines 1185-1192) triggers an automatic fallback to the core satellite catalog. This ensures the visualization remains functional even if CelesTrak endpoints are unreachable, defaulting to the last known good state rather than displaying empty space.
What performance optimizations exist for propagating hundreds of satellites?
The code implements temporal batching via _propagateAll for core satellites and round-robin chunking via _propagateDenseChunk for dense constellations. By distributing the SGP4 computational load across animation frames and caching results in the _catalog Map, the system maintains 60fps while updating approximately 840+ objects every second without blocking the main thread.
Can this TLE propagation code work without Cesium.js?
Yes. The propagatePosition function and parseTLE helper have no Cesium dependencies—they operate purely on satellite.js math utilities returning standard JavaScript numbers and objects. You can use these functions to drive Leaflet maps, Three.js scenes, or custom SVG visualizations by converting the returned longitude/latitude/altitude values to your rendering system's coordinate space.
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 →