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 eciToGeodetic with gstime
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →