Skip to content

karansahani78/realtime-satellite-tracker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

33 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

orbitview-animation

πŸ›° OrbitView β€” Satellite Tracking Platform

Production-ready real-time satellite tracker powered by live TLE data, SGP4 orbit propagation, and WebSocket streaming

Java Spring Boot React PostgreSQL Docker License

SGP4 TLE WebSocket JWT


πŸ›° Satellites Tracked ⚑ Refresh Rate 🎯 SGP4 Accuracy πŸ”­ Prediction Window πŸ“‘ TLE Sync
14,000+ 5 seconds ~2 km (LEO) 72 hours Every 30 min

πŸ“‹ Table of Contents


✨ Features

πŸ›° Core Tracking

  • Tracks 1,000+ satellites using live CelesTrak TLE data
  • Real-time position via SGP4 orbit propagation (~2 km LEO accuracy)
  • 5-second WebSocket position streaming to all clients
  • Ground track visualization on interactive Leaflet world map
  • Satellite sunlight / eclipse status detection
  • Observer azimuth, elevation & range from any ground location

πŸ— Built for Scale

  • Stateless JWT auth β†’ horizontal pod scaling ready
  • Multi-TTL Caffeine cache (5s positions β†’ 6h metadata)
  • Async SGP4 computation thread pool
  • Flyway migrations for zero-downtime schema updates
  • Docker Compose one-command local setup
  • /actuator/health liveness + readiness probes

πŸ”­ Orbit Prediction

  • Future position prediction up to 72 hours ahead
  • Pass prediction over any ground observer location
  • Rise/set azimuth, max elevation, pass duration
  • Visual pass detection: lit satellite + night sky observer
  • Orbital period, apogee, perigee, inclination stats

πŸ” Security

  • JWT access tokens (24h) + refresh tokens (7d)
  • BCrypt password hashing (strength 12)
  • Per-environment CORS configuration
  • Bean Validation on all request bodies
  • Token-bucket rate limiting per IP
  • Parameterized JPA queries (no SQL injection)

πŸ”¬ Advanced Orbital Analysis

OrbitView goes beyond basic position tracking with a full suite of orbital mechanics analysis tools designed for radio operators, conjunction monitoring, and mission planning workflows.


πŸ“» Doppler Shift Calculation

Computes the instantaneous Doppler frequency shift for any satellite relative to a ground observer, enabling accurate radio frequency prediction for uplink/downlink communications.

  • Derives range-rate (radial velocity) from the SGP4-propagated ECI velocity vector and the observer's ECEF position
  • Supports any nominal carrier frequency β€” useful for VHF/UHF amateur passes, L-band telemetry, and S-band downlinks
  • Returns both the shifted receive frequency and the shift magnitude in Hz/kHz
  • Integrates with pass prediction: pre-computes the full Doppler curve across an entire pass so radio operators can configure their SDR or transceiver in advance
Field Description
nominalFreqHz Carrier frequency of the satellite transmitter
dopplerShiftHz Instantaneous frequency shift (negative = approaching)
rangeRateKmSec Radial velocity component toward/away from observer
receivedFreqHz Corrected receive frequency accounting for shift

🚨 Satellite Conjunction Detection

Detects close approaches between any two tracked satellites within a configurable screening distance, providing early warning of potential collision risk or orbital proximity events.

  • Screens all active satellite pairs at configurable time intervals (default: every 15 minutes over a 24-hour window)
  • Computes miss distance in km using ECI-frame relative position vectors
  • Reports Time of Closest Approach (TCA), miss distance, and relative speed at TCA
  • Flags conjunctions below a hard threshold (default: 5 km) as high-risk events
  • Streams high-risk conjunction alerts in real time over the /topic/conjunctions WebSocket topic
  • Persists conjunction events to the database for post-event auditing and trend analysis
Risk Level Miss Distance Action
🟒 Nominal > 25 km Logged only
🟑 Caution 5 – 25 km Logged + WebSocket alert
πŸ”΄ Warning < 5 km Logged + WebSocket alert + email notification

🌐 Orbital Event Detection

Automatically detects and timestamps discrete orbital events as each satellite propagates forward in time, eliminating the need for clients to poll position endpoints for threshold crossings.

  • Apogee / Perigee crossings β€” detected when the radial distance derivative passes through zero; reports exact crossing time, altitude, and current orbital elements
  • Ascending / Descending node crossings β€” detected when the satellite crosses the equatorial plane (latitude sign change); reports RAAN at crossing for orbit counting and repeat-groundtrack analysis
  • Eclipse entry and exit β€” computed via cylindrical Earth shadow model; reports penumbra entry, umbra entry, umbra exit, and penumbra exit timestamps with fractional eclipse depth
  • Events are accumulated per-satellite during propagation sweeps and stored in the orbital_events table for historical queries
Propagation sweep (Tβ‚€ β†’ Tβ‚€+72h, 30s steps)
     β”‚
     β”œβ”€ Apogee/Perigee detector  β†’ orbital_events (type=APOGEE / PERIGEE)
     β”œβ”€ Node crossing detector   β†’ orbital_events (type=ASC_NODE / DESC_NODE)
     └─ Eclipse shadow model     β†’ orbital_events (type=ECLIPSE_ENTRY / ECLIPSE_EXIT)

πŸ“‘ Signal Visibility Analysis

Combines elevation geometry, solar illumination, and observer sky conditions into a single composite visibility score per pass, replacing the simple isVisible boolean with fine-grained signal quality data.

  • Minimum elevation filter (configurable, default 10Β°) eliminates horizon-grazing passes with poor signal-to-noise ratio
  • Sunlight status sourced directly from the SGP4 eclipse model β€” differentiates penumbra from full shadow
  • Observer night condition computed from solar depression angle at the ground station
  • Atmospheric signal path length estimated from elevation angle for link-budget calculations
  • Composite visibilityClass field returned on every pass prediction:
visibilityClass Conditions
OPTICAL_AND_RADIO Satellite lit + observer night + elevation β‰₯ 10Β°
RADIO_ONLY Any elevation β‰₯ 10Β° (regardless of lighting)
MARGINAL Elevation 5°–10Β° (atmospheric degradation likely)
NOT_VISIBLE Below horizon or blocked

πŸ” Ground Station Tracking

Provides a continuous, observer-relative tracking data stream for any registered ground station, going beyond the snapshot AZ/EL values in the /current endpoint.

  • Streams real-time azimuth, elevation, and slant range at the WebSocket broadcast cadence (5 seconds)
  • Computes antenna pointing rate (Β°/second) to support motorized dish controllers and rotor interfaces
  • Calculates two-way light-time delay and one-way propagation delay in milliseconds for timing-sensitive applications
  • Supports multiple simultaneous ground stations per user account β€” each station receives its own /topic/station/{stationId} WebSocket channel
  • Ground station profiles stored in the ground_stations table with geodetic lat/lon/altitude (WGS-84)

πŸ“Š Advanced Orbital Analytics

Exposes derived orbital mechanics quantities beyond the basic position/velocity output of the raw SGP4 propagator.

  • Relative velocity between satellites β€” ECI-frame vector subtraction of two propagated velocity states; useful for rendezvous planning and conjunction severity assessment
  • Specific orbital energy (vis-viva) β€” computed as Ξ΅ = vΒ²/2 βˆ’ ΞΌ/r; a negative value confirms a bound orbit; magnitude indicates altitude regime
  • Semi-major axis β€” derived from mean motion in the TLE via a = (ΞΌ / nΒ²)^(1/3); reported in km alongside the TLE-native mean motion value
  • Inclination comparison across a catalog subset β€” batch endpoint returns inclination distribution for a filtered satellite set, supporting constellation coverage analysis
  • Mean motion drift (αΉ„) β€” first derivative of mean motion from TLE Line 1 field; indicates active manoeuvring or significant drag decay
Analytic Endpoint Notes
Orbital energy GET /satellites/{id}/analytics Returns Ξ΅, a, e, T
Relative velocity GET /satellites/relative?ids=A,B ECI Ξ”V vector + scalar magnitude
Inclination distribution GET /satellites/analytics/inclinations?category=GPS Histogram-ready buckets
Mean motion drift Included in /satellites/{id} detail response From TLE Line 1 field 7

⚑ Real-time Orbital Event Streaming

All orbital events and analysis results are broadcast over dedicated STOMP WebSocket topics so frontends and external consumers receive push notifications without polling.

// Subscribe to conjunction warnings across all tracked satellites
client.subscribe('/topic/conjunctions', (msg) => {
  const { satelliteA, satelliteB, missDistanceKm, tcaUtc, riskLevel } = JSON.parse(msg.body)
  showConjunctionAlert(satelliteA, satelliteB, missDistanceKm, riskLevel)
})

// Subscribe to orbital events for a specific satellite
client.subscribe('/topic/satellite/25544/events', (msg) => {
  const { eventType, eventTimeUtc, altitudeKm } = JSON.parse(msg.body)
  // eventType: APOGEE | PERIGEE | ASC_NODE | DESC_NODE | ECLIPSE_ENTRY | ECLIPSE_EXIT
  logOrbitalEvent(eventType, eventTimeUtc, altitudeKm)
})

// Subscribe to ground station tracking stream
client.subscribe('/topic/station/my-station-id', (msg) => {
  const { azimuthDeg, elevationDeg, rangeKm, rangeRateKmSec, pointingRateDegSec } = JSON.parse(msg.body)
  updateAntennaDish(azimuthDeg, elevationDeg)
})

WebSocket topic reference β€” advanced channels:

Topic Cadence Payload
/topic/conjunctions On detection Conjunction summary + risk level
/topic/satellite/{id}/events On event crossing Event type, time, orbital state
/topic/station/{stationId} Every 5 seconds AZ, EL, range, range-rate, pointing rate
/topic/satellites/eclipses On shadow boundary Satellite ID, shadow type, entry/exit time

πŸ— Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       CLIENT LAYER                           β”‚
β”‚   βš› React 18 + Vite  β”‚  πŸ—Ί Leaflet Map  β”‚  ⚑ STOMP WS      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚  HTTPS / WSS
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     GATEWAY LAYER                            β”‚
β”‚             βš™ Nginx β€” SSL Termination + WS Upgrade           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚  HTTP/1.1 + WebSocket
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   APPLICATION LAYER                          β”‚
β”‚  β˜• Spring Boot 3  β”‚  πŸ” JWT  β”‚  πŸ›Έ SGP4  β”‚  πŸ“‘ TLE Service  β”‚
β”‚                    πŸ“’ STOMP In-Memory Broker                  β”‚
β”‚  πŸ”¬ Doppler  β”‚  🚨 Conjunction  β”‚  🌐 Events  β”‚  πŸ“Š Analytics β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚  JPA / JDBC                     β”‚  WebFlux HTTP
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  🐘 PostgreSQL 16   β”‚          β”‚   🌍 CelesTrak API          β”‚
β”‚  ⚑ Caffeine Cache  β”‚          β”‚   (Live TLE Data)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key architecture decisions:

Decision Why
Stateless JWT Every backend pod validates tokens independently β€” zero-config horizontal scaling
SGP4 from scratch All public TLE data is tuned for SGP4 specifically β€” any other propagator gives wrong results
STOMP over raw WebSocket Built-in pub/sub topics, SockJS fallback, first-class Spring integration
Caffeine per-cache TTL Positions stale in 5s; satellite metadata valid 6h β€” one global TTL wastes resources
ConcurrentHashMap TLE cache O(1) lookup during propagation avoids DB round-trip on every position request
Nginx reverse proxy Single TLS certificate, WebSocket upgrade, and static file serving in one place
Event detection in propagation sweep Apogee/perigee, node crossings, and eclipse transitions are detected in a single forward sweep rather than separate passes β€” avoids redundant SGP4 calls and keeps CPU cost O(nΒ·steps) regardless of how many event types are active
Conjunction screening via bounding-box pre-filter Before computing exact miss distances, satellites are binned into spatial cells; only pairs sharing a cell proceed to the full ECI-frame closest-approach calculation β€” reduces O(nΒ²) pair comparisons by ~95% for typical catalog sizes

Scaling path: Swap Caffeine β†’ Redis for shared cache across instances. Add a Kafka topic for WebSocket fan-out. Deploy on AWS ECS behind an ALB β€” no application code changes required.


πŸ”© Tech Stack

πŸ–₯ Backend β€” Java / Spring Boot
Library Version Purpose
Java 17 LTS Language β€” records, sealed classes, text blocks
Spring Boot 3.2.1 Framework, auto-configuration, embedded Tomcat
Spring Security 6.x JWT filter chain, method-level @PreAuthorize
Spring Data JPA 3.2.1 ORM, repository pattern, JPQL queries
Spring WebFlux 3.2.1 Reactive HTTP client for TLE fetching
Spring WebSocket 3.2.1 STOMP message broker for live streaming
Flyway 10.x Versioned SQL schema migrations
JJWT 0.12.3 JWT generation + validation (HS256)
Caffeine 3.x High-performance in-process cache
HikariCP 5.x JDBC connection pool
SpringDoc OpenAPI 2.3.0 Auto-generated Swagger UI
Lombok 1.18.x Builders, getters, @Slf4j boilerplate reduction
βš› Frontend β€” React / Vite
Library Version Purpose
React 18.2 UI framework, concurrent rendering
Vite 5.x Build tool, HMR, code splitting
React Leaflet 4.x Interactive world map with satellite overlays
Zustand 4.x Lightweight global state (auth + satellite data)
@stomp/stompjs 7.x STOMP WebSocket client
SockJS-client 1.6.x WebSocket transport fallback
Recharts 2.x Altitude / velocity time-series charts
Tailwind CSS 3.4 Utility-first dark-theme styling
Axios 1.6 HTTP client with JWT interceptors + auto-refresh
date-fns 3.x UTC-safe date formatting
Lucide React 0.303 Icon system
πŸ— Infrastructure
Tool Purpose
Docker + Compose Container orchestration, one-command local setup
PostgreSQL 16 Primary database β€” JSONB, GIN indexes, TIMESTAMPTZ
Nginx Reverse proxy, SSL termination, WebSocket upgrade
Flyway Schema version control β€” auto-runs on startup
Spring Actuator /health, /metrics for load balancer probes
Maven Dependency management, multi-stage Docker build

πŸš€ Quick Start

Prerequisites

🐳 Docker 24+ and Docker Compose v2 β€” no local Java or Node required, everything runs in containers.

Option A β€” Docker (Recommended)

# 1. Clone the repository
git clone https://github.com/yourusername/satellite-tracker.git
cd satellite-tracker

# 2. Copy environment config and set your secrets
cp .env.example .env
# Edit .env: set JWT_SECRET to $(openssl rand -base64 64)

# 3. Start the full stack
docker compose up --build -d

# 4. Watch startup logs β€” Flyway runs migrations automatically
docker compose logs -f backend

# 5. Open the app
open http://localhost                     # React frontend
open http://localhost/swagger-ui.html     # Swagger API explorer

⚠️ First boot: fetches live TLE data from CelesTrak (~30 seconds). Falls back to demo TLEs (ISS, Hubble, CSS) if CelesTrak is temporarily unavailable.


Option B β€” Local Development

Requirements: Java 17+, Node 20+, PostgreSQL 14+

# 1. Create the database
createdb satellite_tracker
psql satellite_tracker -c "CREATE USER satellite_user WITH PASSWORD 'satellite_pass';"
psql satellite_tracker -c "GRANT ALL PRIVILEGES ON DATABASE satellite_tracker TO satellite_user;"

# 2. Start the backend (port 8080)
cd backend
./mvnw spring-boot:run

# 3. Start the frontend (port 3000) β€” new terminal
cd frontend
npm install
npm run dev

# 4. Verify a live position
curl http://localhost:8080/api/satellites/25544/current

Option C β€” Railway (One-Click Cloud)

npm install -g @railway/cli
railway login
railway init
railway up

Set these in your Railway dashboard:

DATABASE_URL         = postgresql://user:pass@host/db
JWT_SECRET           = <openssl rand -base64 64>
CORS_ALLOWED_ORIGINS = https://your-frontend.up.railway.app
SERVER_PORT          = 8080

πŸ“‘ API Reference

Base URL: http://localhost:8080/api
Swagger UI: http://localhost:8080/swagger-ui.html

πŸ›° Satellite Catalog

Method Endpoint Description
GET /satellites Paginated catalog. ?query=ISS&category=Weather&activeOnly=true&page=0&size=20
GET /satellites/featured Notable satellites: ISS, Hubble, CSS Tianhe, NOAA, GPS
GET /satellites/{noradId} Full detail + current TLE
GET /categories All distinct satellite categories

πŸ“ Position & Tracking

Method Endpoint Description
GET /satellites/{noradId}/current Real-time position. Add ?lat=51.5&lon=-0.12 for AZ/EL/range
GET /satellites/{noradId}/predict Future position. ?minutes=90 (max 4320 = 72h)
GET /satellites/{noradId}/track Ground track polyline. ?start=&end=&intervalSeconds=30
GET /satellites/{noradId}/passes Pass predictions. ?lat=51.5&lon=-0.12&hours=24&minElevation=10
GET /satellites/positions/all All satellite positions for map overview. ?limit=100
πŸ“¦ Example: Current position response
{
  "noradCatalogId": 25544,
  "name": "ISS (ZARYA)",
  "timestamp": "2024-01-15T14:32:07.123Z",
  "latitude": 51.6234,
  "longitude": -12.4521,
  "altitudeKm": 418.3,
  "velocityKmPerSec": 7.66,
  "azimuth": 234.1,
  "elevation": 42.7,
  "rangeKm": 612.4,
  "isDaylight": true,
  "isVisible": true
}
πŸ“¦ Example: Pass prediction response
{
  "noradCatalogId": 25544,
  "name": "ISS (ZARYA)",
  "riseTime": "2024-01-15T19:42:00Z",
  "maxElevationTime": "2024-01-15T19:45:30Z",
  "setTime": "2024-01-15T19:49:00Z",
  "maxElevationDeg": 68.4,
  "riseAzimuthDeg": 311.2,
  "setAzimuthDeg": 127.8,
  "durationSeconds": 420,
  "isVisualPass": true,
  "isDaylightPass": true,
  "isObserverNight": true
}

πŸ”¬ Advanced Orbital Analysis Endpoints

Method Endpoint Description
GET /satellites/{noradId}/doppler Doppler shift at current time. ?lat=&lon=&freqHz=437550000
GET /satellites/{noradId}/passes/doppler Full Doppler curve across next pass. ?lat=&lon=&freqHz=
GET /satellites/{noradId}/analytics Orbital energy, semi-major axis, mean motion drift
GET /satellites/{noradId}/events Historical orbital events (apogee, node crossings, eclipses). ?hours=24
GET /satellites/relative Relative velocity between two satellites. ?ids=25544,20580
GET /satellites/analytics/inclinations Inclination distribution for a satellite category. ?category=GPS
GET /conjunctions Active conjunction warnings. ?minRiskLevel=CAUTION&hours=24
GET /conjunctions/{id} Detailed conjunction report with TCA, miss distance, and relative velocity
GET /ground-stations List registered ground stations for the authenticated user
POST /ground-stations Register a new ground station. Body: {name, lat, lon, altitudeM}
DELETE /ground-stations/{stationId} Remove a ground station
πŸ“¦ Example: Doppler shift response
{
  "noradCatalogId": 25544,
  "name": "ISS (ZARYA)",
  "timestamp": "2024-01-15T14:32:07.123Z",
  "nominalFreqHz": 437550000,
  "dopplerShiftHz": -3241,
  "receivedFreqHz": 437546759,
  "rangeRateKmSec": -2.228,
  "rangeKm": 612.4,
  "elevationDeg": 42.7
}
πŸ“¦ Example: Conjunction warning response
{
  "conjunctionId": "conj-20240115-001",
  "satelliteA": { "noradId": 25544, "name": "ISS (ZARYA)" },
  "satelliteB": { "noradId": 43205, "name": "COSMOS 2519" },
  "timeOfClosestApproach": "2024-01-15T22:17:43Z",
  "missDistanceKm": 3.82,
  "relativeVelocityKmSec": 14.3,
  "riskLevel": "WARNING",
  "detectedAt": "2024-01-15T14:00:00Z"
}

πŸ” Authentication

Method Endpoint Description
POST /auth/register Register. Body: {username, email, password, displayName}
POST /auth/login Login. Body: {usernameOrEmail, password} β†’ returns JWT tokens
POST /auth/refresh Refresh tokens. Body: {refreshToken}
GET /auth/me Current user info (requires Authorization: Bearer <token>)
PUT /auth/preferences Update observer location, timezone, theme
POST /auth/favorites/{noradId} Add satellite to favorites
DELETE /auth/favorites/{noradId} Remove satellite from favorites

⚑ WebSocket (STOMP)

Connect to ws://localhost:8080/ws with SockJS fallback:

import { Client } from '@stomp/stompjs'
import SockJS from 'sockjs-client'

const client = new Client({
  webSocketFactory: () => new SockJS('http://localhost:8080/ws'),
  onConnect: () => {

    // All satellite positions β€” server broadcasts every 5 seconds
    client.subscribe('/topic/satellites/all', (msg) => {
      const { positions, satelliteCount, timestamp } = JSON.parse(msg.body)
      updateMap(positions) // positions: [{noradId, lat, lon, alt, ...}]
    })

    // Track one specific satellite
    client.subscribe('/topic/satellite/25544', handleISSUpdate)
    client.publish({
      destination: '/app/track',
      body: JSON.stringify({ noradId: 25544 })
    })
  }
})

client.activate()

🐘 Database Schema

satellites ──< tle_records
     β”‚
     └──< tracking_logs >── users
     β”‚                         β”‚
     └──< orbital_events    user_roles
                             user_favorite_satellites
                             ground_stations
πŸ“„ Full schema β€” key tables
-- Satellite master catalog
CREATE TABLE satellites (
    id                     BIGSERIAL    PRIMARY KEY,
    norad_catalog_id       INTEGER      NOT NULL UNIQUE,  -- ISS=25544, Hubble=20580
    cospar_id              VARCHAR(20),                   -- e.g. "1998-067A"
    name                   VARCHAR(100) NOT NULL,
    category               VARCHAR(100),
    orbital_period_minutes DOUBLE PRECISION,
    inclination_deg        DOUBLE PRECISION,
    apogee_km              DOUBLE PRECISION,
    perigee_km             DOUBLE PRECISION,
    is_active              BOOLEAN      NOT NULL DEFAULT true,
    created_at             TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    updated_at             TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

-- GIN index: fast full-text satellite name search
CREATE INDEX idx_satellite_name ON satellites
    USING gin(to_tsvector('english', name));

-- TLE history β€” only one is_current=true per satellite at any time
CREATE TABLE tle_records (
    id           BIGSERIAL   PRIMARY KEY,
    satellite_id BIGINT      NOT NULL REFERENCES satellites(id),
    line1        VARCHAR(70) NOT NULL,   -- NORAD TLE Line 1 (69 chars)
    line2        VARCHAR(70) NOT NULL,   -- NORAD TLE Line 2 (69 chars)
    epoch        TIMESTAMPTZ NOT NULL,
    is_current   BOOLEAN     NOT NULL DEFAULT false,
    fetched_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Partial index: instant current-TLE lookup without full scan
CREATE INDEX idx_tle_satellite_current
    ON tle_records(satellite_id, is_current)
    WHERE is_current = true;

-- Users table
CREATE TABLE users (
    id                   BIGSERIAL    PRIMARY KEY,
    username             VARCHAR(50)  NOT NULL UNIQUE,
    email                VARCHAR(100) NOT NULL UNIQUE,
    password_hash        VARCHAR(255) NOT NULL,
    observer_latitude    DOUBLE PRECISION,
    observer_longitude   DOUBLE PRECISION,
    observer_altitude_m  INTEGER DEFAULT 0,
    timezone             VARCHAR(50)  DEFAULT 'UTC',
    theme                VARCHAR(20)  DEFAULT 'dark',
    created_at           TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    last_login_at        TIMESTAMPTZ
);

-- Ground stations β€” per-user observer profiles for tracking and Doppler
CREATE TABLE ground_stations (
    id           BIGSERIAL        PRIMARY KEY,
    user_id      BIGINT           NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name         VARCHAR(100)     NOT NULL,
    latitude     DOUBLE PRECISION NOT NULL,
    longitude    DOUBLE PRECISION NOT NULL,
    altitude_m   INTEGER          NOT NULL DEFAULT 0,
    created_at   TIMESTAMPTZ      NOT NULL DEFAULT NOW()
);

-- Orbital events β€” apogee/perigee crossings, node crossings, eclipse transitions
CREATE TABLE orbital_events (
    id           BIGSERIAL   PRIMARY KEY,
    satellite_id BIGINT      NOT NULL REFERENCES satellites(id),
    event_type   VARCHAR(30) NOT NULL,   -- APOGEE | PERIGEE | ASC_NODE | DESC_NODE | ECLIPSE_ENTRY | ECLIPSE_EXIT
    event_time   TIMESTAMPTZ NOT NULL,
    altitude_km  DOUBLE PRECISION,
    latitude     DOUBLE PRECISION,
    longitude    DOUBLE PRECISION,
    extra_data   JSONB,                  -- eclipse depth, RAAN at node, etc.
    detected_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orbital_events_satellite_time
    ON orbital_events(satellite_id, event_time DESC);

-- Conjunction warnings β€” close approach records between satellite pairs
CREATE TABLE conjunctions (
    id                    BIGSERIAL        PRIMARY KEY,
    satellite_a_id        BIGINT           NOT NULL REFERENCES satellites(id),
    satellite_b_id        BIGINT           NOT NULL REFERENCES satellites(id),
    time_of_closest_approach TIMESTAMPTZ  NOT NULL,
    miss_distance_km      DOUBLE PRECISION NOT NULL,
    relative_velocity_km_sec DOUBLE PRECISION,
    risk_level            VARCHAR(20)      NOT NULL,  -- NOMINAL | CAUTION | WARNING
    detected_at           TIMESTAMPTZ      NOT NULL DEFAULT NOW(),
    resolved_at           TIMESTAMPTZ
);

CREATE INDEX idx_conjunctions_tca ON conjunctions(time_of_closest_approach DESC);
CREATE INDEX idx_conjunctions_risk ON conjunctions(risk_level) WHERE resolved_at IS NULL;

Schema design decisions:

Decision Rationale
NORAD ID β‰  DB PK NORAD ID is the universal external identifier; internal BIGSERIAL PK stays stable
Orbital elements on satellites Denormalized for fast catalog queries without a join on every request
is_current partial index Tiny index vs full table scan β€” only one row per satellite matches
TIMESTAMPTZ everywhere Forces UTC storage; eliminates daylight-saving bugs in pass prediction
GIN index on satellite name Supports LIKE '%query%' searches efficiently
orbital_events JSONB extra_data Stores event-type-specific fields (eclipse depth, RAAN) without schema churn
conjunctions unresolved partial index Keeps the active-warning query fast without scanning historical resolved rows

Flyway migrations live in backend/src/main/resources/db/migration/. Add V2__description.sql files for future changes β€” they run automatically on startup.


πŸ“ Project Structure

β˜• Backend β€” Java package layout
backend/src/main/java/com/satellitetracker/
β”‚
β”œβ”€β”€ SatelliteTrackerApplication.java      ← @SpringBootApplication entry point
β”‚
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ SecurityConfig.java               ← JWT filter chain, CORS, endpoint rules
β”‚   β”œβ”€β”€ WebSocketConfig.java              ← STOMP broker, SockJS endpoint
β”‚   β”œβ”€β”€ CacheConfig.java                  ← Caffeine per-cache TTL policies
β”‚   └── AppConfig.java                    ← WebClient bean for CelesTrak requests
β”‚
β”œβ”€β”€ controller/
β”‚   β”œβ”€β”€ SatelliteController.java          ← REST /api/satellites/**
β”‚   β”œβ”€β”€ AuthController.java               ← REST /api/auth/**
β”‚   β”œβ”€β”€ ConjunctionController.java        ← REST /api/conjunctions/**
β”‚   β”œβ”€β”€ GroundStationController.java      ← REST /api/ground-stations/**
β”‚   └── SatelliteWebSocketController.java ← Scheduled broadcast + @MessageMapping
β”‚
β”œβ”€β”€ service/
β”‚   β”œβ”€β”€ TleService.java                   ← CelesTrak fetch, parse, cache, schedule
β”‚   β”œβ”€β”€ OrbitPropagationService.java      ← SGP4 positions, tracks, pass prediction
β”‚   β”œβ”€β”€ OrbitalEventService.java          ← Apogee/perigee, node, eclipse detection
β”‚   β”œβ”€β”€ ConjunctionService.java           ← Pairwise close-approach screening + alerts
β”‚   β”œβ”€β”€ DopplerService.java               ← Range-rate and frequency shift computation
β”‚   β”œβ”€β”€ GroundStationService.java         ← Observer tracking, pointing rate, link delay
β”‚   β”œβ”€β”€ OrbitalAnalyticsService.java      ← Energy, semi-major axis, relative velocity
β”‚   β”œβ”€β”€ SatelliteService.java             ← Catalog search, featured, categories
β”‚   └── UserService.java                  ← Register, login, JWT, preferences
β”‚
β”œβ”€β”€ model/
β”‚   β”œβ”€β”€ entity/
β”‚   β”‚   β”œβ”€β”€ Satellite.java                ← @Entity: satellites table
β”‚   β”‚   β”œβ”€β”€ TleRecord.java                ← @Entity: tle_records table
β”‚   β”‚   β”œβ”€β”€ User.java                     ← @Entity: users table
β”‚   β”‚   β”œβ”€β”€ GroundStation.java            ← @Entity: ground_stations table
β”‚   β”‚   β”œβ”€β”€ OrbitalEvent.java             ← @Entity: orbital_events table
β”‚   β”‚   β”œβ”€β”€ Conjunction.java              ← @Entity: conjunctions table
β”‚   β”‚   └── TrackingLog.java              ← @Entity: tracking_logs table
β”‚   └── dto/
β”‚       β”œβ”€β”€ SatelliteDto.java             ← Summary, Detail, TleDto
β”‚       β”œβ”€β”€ OrbitDto.java                 ← Position, OrbitTrack, PassPrediction
β”‚       β”œβ”€β”€ DopplerDto.java               ← DopplerShift, DopplerCurve
β”‚       β”œβ”€β”€ ConjunctionDto.java           ← ConjunctionSummary, ConjunctionDetail
β”‚       β”œβ”€β”€ OrbitalAnalyticsDto.java      ← Energy, SemiMajorAxis, RelativeVelocity
β”‚       └── AuthDto.java                  ← LoginRequest, RegisterRequest, AuthResponse
β”‚
β”œβ”€β”€ repository/
β”‚   β”œβ”€β”€ SatelliteRepository.java          ← JPA: fulltext search, category filter
β”‚   β”œβ”€β”€ TleRecordRepository.java          ← JPA: current TLE, markAllNotCurrent
β”‚   β”œβ”€β”€ UserRepository.java               ← JPA: findByUsernameOrEmail
β”‚   β”œβ”€β”€ GroundStationRepository.java      ← JPA: findByUserId
β”‚   β”œβ”€β”€ OrbitalEventRepository.java       ← JPA: findBySatelliteAndEventTimeBetween
β”‚   β”œβ”€β”€ ConjunctionRepository.java        ← JPA: active warnings, risk level filter
β”‚   └── TrackingLogRepository.java
β”‚
β”œβ”€β”€ security/
β”‚   β”œβ”€β”€ JwtUtils.java                     ← HS256 token generation + validation
β”‚   β”œβ”€β”€ JwtAuthenticationFilter.java      ← OncePerRequestFilter: extract & validate JWT
β”‚   β”œβ”€β”€ JwtAuthenticationEntryPoint.java  ← 401 JSON error response
β”‚   └── UserDetailsServiceImpl.java       ← Spring Security UserDetails bridge
β”‚
β”œβ”€β”€ util/
β”‚   β”œβ”€β”€ Sgp4Propagator.java               ← β˜… Complete SGP4 algorithm (Vallado 2006, ~750 LOC)
β”‚   β”œβ”€β”€ TleParser.java                    ← 2/3-line TLE format parser + checksum validation
β”‚   β”œβ”€β”€ AstroUtils.java                   ← Sun position, AZ/EL/range, GMST, ECEF conversion
β”‚   └── DopplerUtils.java                 ← Range-rate projection, relativistic correction
β”‚
└── exception/
    β”œβ”€β”€ GlobalExceptionHandler.java       ← @RestControllerAdvice, RFC 7807 error format
    β”œβ”€β”€ ResourceNotFoundException.java    ← 404
    β”œβ”€β”€ TleParseException.java            ← 422
    └── ConflictException.java            ← 409
βš› Frontend β€” React source layout
frontend/src/
β”‚
β”œβ”€β”€ main.jsx                    ← ReactDOM.createRoot, BrowserRouter
β”œβ”€β”€ App.jsx                     ← Routes, WebSocket init, global data fetch
β”œβ”€β”€ index.css                   ← Tailwind directives + Leaflet dark overrides
β”‚
β”œβ”€β”€ pages/
β”‚   β”œβ”€β”€ MapPage.jsx              ← World map + live satellite overlay + sidebar
β”‚   β”œβ”€β”€ SatellitesPage.jsx       ← Searchable paginated satellite catalog
β”‚   β”œβ”€β”€ SatelliteDetailPage.jsx  ← Detail panel + orbit chart + pass predictions
β”‚   β”œβ”€β”€ PassesPage.jsx           ← Observer pass predictor with location input
β”‚   β”œβ”€β”€ ConjunctionsPage.jsx     ← Live conjunction warning dashboard
β”‚   β”œβ”€β”€ AnalyticsPage.jsx        ← Orbital analytics: energy, inclination, Doppler
β”‚   └── LoginPage.jsx            ← JWT login / registration forms
β”‚
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ map/
β”‚   β”‚   β”œβ”€β”€ SatelliteMap.jsx     ← Leaflet MapContainer with dark tile layer
β”‚   β”‚   β”œβ”€β”€ SatelliteMarker.jsx  ← Animated divIcon + info popup
β”‚   β”‚   └── OrbitPath.jsx        ← Ground track Polyline, Β±180Β° date-line split
β”‚   β”œβ”€β”€ satellite/
β”‚   β”‚   β”œβ”€β”€ SatellitePanel.jsx   ← Sidebar: live position stats + controls
β”‚   β”‚   β”œβ”€β”€ SatelliteCard.jsx    ← Catalog grid card with orbital parameters
β”‚   β”‚   β”œβ”€β”€ PassTable.jsx        ← Upcoming passes with rise/set/max-elevation
β”‚   β”‚   β”œβ”€β”€ DopplerChart.jsx     ← Recharts Doppler curve across pass window
β”‚   β”‚   β”œβ”€β”€ OrbitalEventLog.jsx  ← Timestamped apogee/node/eclipse event feed
β”‚   β”‚   └── OrbitalChart.jsx     ← Recharts altitude + velocity over time
β”‚   β”œβ”€β”€ conjunction/
β”‚   β”‚   β”œβ”€β”€ ConjunctionAlert.jsx ← Toast notification for WARNING-level events
β”‚   β”‚   └── ConjunctionTable.jsx ← Active close-approach table with risk badges
β”‚   β”œβ”€β”€ groundstation/
β”‚   β”‚   β”œβ”€β”€ StationManager.jsx   ← CRUD UI for ground station profiles
β”‚   β”‚   └── StationTracker.jsx   ← Live AZ/EL/range display + pointing rate
β”‚   β”œβ”€β”€ layout/
β”‚   β”‚   └── Layout.jsx           ← Top nav bar + live/offline status indicator
β”‚   └── ui/
β”‚       β”œβ”€β”€ TimeSlider.jsx       ← Β±72h time scrubber for position prediction
β”‚       β”œβ”€β”€ SearchBar.jsx        ← Debounced satellite search input
β”‚       └── LoadingSpinner.jsx
β”‚
β”œβ”€β”€ store/
β”‚   └── index.js                 ← Zustand: useAuthStore + useSatelliteStore + useConjunctionStore
β”‚
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ api.js                   ← Axios instance, JWT interceptors, auto-refresh
β”‚   └── websocket.js             ← STOMP client: connect, subscribe, reconnect
β”‚
└── utils/
    └── formatters.js            ← Coordinate, speed, time, magnitude, frequency formatters
πŸ“ Monorepo root
satellite-tracker/
β”œβ”€β”€ satellite-animation.svg      ← Animated orbital diagram (this header)
β”œβ”€β”€ docker-compose.yml           ← Full stack: postgres + backend + frontend + nginx
β”œβ”€β”€ docker-compose.prod.yml      ← Production overrides (resource limits, restart policy)
β”œβ”€β”€ .env.example                 ← Environment variable template
β”œβ”€β”€ README.md
β”œβ”€β”€ LICENSE
β”‚
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ Dockerfile               ← Multi-stage: Maven build β†’ JRE 17 slim runtime
β”‚   β”œβ”€β”€ pom.xml
β”‚   └── src/
β”‚       β”œβ”€β”€ main/java/           ← Application source
β”‚       β”œβ”€β”€ main/resources/
β”‚       β”‚   β”œβ”€β”€ application.properties
β”‚       β”‚   └── db/migration/
β”‚       β”‚       β”œβ”€β”€ V1__initial_schema.sql
β”‚       β”‚       └── V2__advanced_orbital_tables.sql  ← ground_stations, orbital_events, conjunctions
β”‚       └── test/
β”‚
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ Dockerfile               ← Multi-stage: Node build β†’ Nginx static serve
β”‚   β”œβ”€β”€ vite.config.js
β”‚   β”œβ”€β”€ tailwind.config.js
β”‚   └── src/
β”‚
β”œβ”€β”€ nginx/
β”‚   └── nginx.conf               ← Reverse proxy + WebSocket upgrade headers
β”‚
└── database/
    └── init.sql                 ← DB user creation + initial permissions

🐳 Docker & Deployment

πŸ“„ docker-compose.yml β€” full stack
version: '3.9'

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: satellite_tracker
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME}"]
      interval: 10s
      retries: 5
    networks: [satellite-net]

  backend:
    build: ./backend
    environment:
      DATABASE_URL: jdbc:postgresql://postgres:5432/satellite_tracker
      DATABASE_USERNAME: ${DATABASE_USERNAME}
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}
      CORS_ALLOWED_ORIGINS: http://localhost
    depends_on:
      postgres: { condition: service_healthy }
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      retries: 3
    networks: [satellite-net]

  frontend:
    build:
      context: ./frontend
      args:
        VITE_API_URL: ""    # empty = relative URLs proxied through nginx
    networks: [satellite-net]

  nginx:
    image: nginx:alpine
    ports: ["80:80", "443:443"]
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on: [backend, frontend]
    networks: [satellite-net]

volumes:
  pgdata:
networks:
  satellite-net:
πŸ‹ Backend Dockerfile β€” multi-stage build
# Stage 1: Build with full JDK + Maven
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn -f pom.xml clean package -DskipTests

# Stage 2: Minimal JRE runtime (~180MB vs ~420MB JDK)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=30s --start-period=60s \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
βš™ nginx.conf β€” reverse proxy + WebSocket upgrade
upstream backend  { server backend:8080; }
upstream frontend { server frontend:80; }

server {
  listen 80;

  # React SPA β€” serve index.html for all routes
  location / {
    proxy_pass http://frontend;
    try_files $uri $uri/ /index.html;
  }

  # Spring Boot REST API
  location /api/ {
    proxy_pass http://backend;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  }

  # STOMP WebSocket (SockJS + raw WS)
  location /ws/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout  86400s;
    proxy_send_timeout  86400s;
  }

  # Swagger UI
  location /swagger-ui/ {
    proxy_pass http://backend/swagger-ui/;
  }
}

☁ Cloud Deployment Steps

Step Action
1. Database Provision managed Postgres (Railway, Render, AWS RDS Free Tier, or Supabase)
2. Secrets openssl rand -base64 64 for JWT_SECRET β€” store in platform secrets vault
3. Backend Deploy backend/Dockerfile β€” Flyway runs V1__initial_schema.sql on first boot
4. Frontend Deploy frontend/Dockerfile with VITE_API_URL=https://your-backend.com as build arg
5. Verify GET /actuator/health β†’ {"status":"UP","components":{"db":{"status":"UP"}}}

βš™ Environment Variables

Copy .env.example β†’ .env and fill in your values:

# ── Database ─────────────────────────────────────────────────
DATABASE_URL=jdbc:postgresql://localhost:5432/satellite_tracker
DATABASE_USERNAME=satellite_user
DATABASE_PASSWORD=changeme_in_production
DB_POOL_SIZE=20

# ── JWT β€” generate before deploying! ─────────────────────────
# Run: openssl rand -base64 64
JWT_SECRET=your-256-bit-base64-encoded-secret-here
JWT_EXPIRATION_MS=86400000          # 24 hours
JWT_REFRESH_EXPIRATION_MS=604800000 # 7 days

# ── CORS ─────────────────────────────────────────────────────
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://yoursite.com

# ── TLE Data ─────────────────────────────────────────────────
TLE_REFRESH_INTERVAL=30             # Minutes between CelesTrak syncs

# ── Rate Limiting ─────────────────────────────────────────────
RATE_LIMIT_RPM=60
RATE_LIMIT_BURST=10

# ── Advanced Orbital Analysis ────────────────────────────────
CONJUNCTION_SCREEN_INTERVAL_MIN=15  # Minutes between conjunction screening sweeps
CONJUNCTION_WARNING_THRESHOLD_KM=5  # Miss distance threshold for WARNING alerts
CONJUNCTION_CAUTION_THRESHOLD_KM=25 # Miss distance threshold for CAUTION alerts
EVENT_DETECTION_STEP_SECONDS=30     # Propagation step size for orbital event sweeps

# ── Server ───────────────────────────────────────────────────
SERVER_PORT=8080
SHOW_SQL=false                      # Set true for SQL query debugging

πŸ”΄ Security checklist before going live:

  • Change JWT_SECRET from the placeholder β€” use openssl rand -base64 64
  • Change DATABASE_PASSWORD from changeme
  • Remove or change the seeded admin account (default: admin / Admin@123!)
  • Restrict CORS_ALLOWED_ORIGINS to your exact production frontend URL
  • Enable HTTPS on your load balancer or via Let's Encrypt / Certbot

πŸ›Έ How SGP4 Works

The core of this platform is a complete from-scratch SGP4 implementation in Java (Sgp4Propagator.java, ~750 lines), based on Vallado et al. "Revisiting Spacetrack Report #3", AIAA 2006-6753.

Why SGP4 specifically? All public TLE data is deliberately tuned to the SGP4 force model. TLE elements are mean elements that absorb modelling errors as corrections β€” using any other propagator (Cowell, Runge-Kutta, etc.) with TLE data produces incorrect positions because the data and the algorithm are mathematically coupled.

Propagation pipeline:

TLE (line1 + line2)
       β”‚
       β–Ό
Parse mean orbital elements
(inclination, RAAN, eccentricity, arg of perigee, mean anomaly, mean motion, B*)
       β”‚
       β–Ό
SGP4 Initialization β€” compute secular drift rates due to:
  β€’ J2, J3, J4 Earth oblateness perturbations
  β€’ Atmospheric drag (B* drag term)
  β€’ Resonance classification: LEO vs deep-space
       β”‚
       β–Ό
Propagate to target time (minutesFromEpoch):
  β€’ Apply secular drift (mean motion, nodal regression, perigee precession)
  β€’ Solve Kepler's equation via Newton-Raphson iteration
  β€’ Apply short-period J2 corrections
       β”‚
       β–Ό
ECI state vector: [x, y, z km] + [vx, vy, vz km/s]
       β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                                                          β”‚
       β–Ό                                                          β–Ό
Rotate ECI β†’ ECEF via GMST                          Advanced analysis layer
Apply Bowring iterative method                        β€’ Doppler: project ECI velocity onto
β†’ Geodetic lat/lon/alt (WGS-84)                        observer LOS vector β†’ range-rate β†’
                                                         Ξ”f = fβ‚€ Β· (rangeRate / c)
                                                      β€’ Eclipse: cylindrical shadow model
                                                         β†’ penumbra / umbra depth scalar
                                                      β€’ Conjunction: ECI-frame Ξ”R between
                                                         two propagated state vectors
                                                      β€’ Orbital energy: Ξ΅ = vΒ²/2 βˆ’ ΞΌ/r

Usage example:

// Initialize from raw TLE strings
Sgp4Propagator sgp4 = new Sgp4Propagator(line1, line2);

// Minutes since TLE epoch (negative = past, positive = future)
double jd = AstroUtils.toJulianDate(Instant.now());
double minutesFromEpoch = (jd - sgp4.getEpochJulianDate()) * 1440.0;

// Propagate β†’ ECI state vector
EciState eci = sgp4.propagate(minutesFromEpoch);

// Convert to geodetic coordinates (WGS-84)
GeodeticPosition geo = Sgp4Propagator.eciToGeodetic(eci.positionArray(), jd);

System.out.printf("ISS: %.4fΒ°N  %.4fΒ°E  %.1f km alt  %.2f km/s%n",
    geo.latitude(), geo.longitude(), geo.altitudeKm(), eci.speed());
// ISS: 51.6234Β°N  -12.4521Β°E  418.3 km alt  7.66 km/s

// Doppler shift for a 437.550 MHz uplink from a ground observer
double dopplerHz = DopplerUtils.computeShiftHz(eci, observerEcef, 437_550_000.0);
System.out.printf("Doppler shift: %+.0f Hz  β†’  receive on %.3f MHz%n",
    dopplerHz, (437_550_000.0 + dopplerHz) / 1e6);
// Doppler shift: -3241 Hz  β†’  receive on 437.547 MHz

Accuracy by TLE age:

TLE Age Expected Position Error
< 24 hours ~1–3 km
1–3 days ~5–15 km
7 days ~10–30 km
> 30 days ⚠️ Unreliable

πŸ“œ License

This project is licensed under the MIT License β€” see the LICENSE file for details.

MIT License β€” free to use, modify, and distribute with attribution.

Built with β˜• Java + βš› React

TLE data from CelesTrak Β· SGP4 algorithm by Vallado et al. (2006)

Give ⭐ Star this repo if you found it useful

About

Real-time Satellite Tracking System built with Spring Boot and TypeScript using TLE data, SGP4 propagation, and Orekit for orbital mechanics. Features include live orbit visualization, pass prediction, conjunction monitoring, Doppler shift calculation, and alert notifications.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors