Skip to content
All projects

_02.

OrbitTrack

Predicts when satellites pass over any ground station, with rise, peak and set times, sky paths and ground tracks, as a web app, JSON API and CLI.

Hosted on Render's free tier, so the first load can take up to a minute while it wakes up.

OrbitTrack web app showing prediction parameters and a countdown to the ISS's next pass over West Lafayette, IN.

01Problem

To hear a weather satellite or talk to the ISS on amateur radio, a ground station needs to know exactly when a satellite will be overhead and where to point. Passes last only a few minutes, and they depend on the station's location and the latest orbital elements.

02What I built

  • Pass prediction for any satellite in six CelesTrak groups (space stations, brightest, weather, amateur radio, science, GPS) from any location: rise, peak and set times, max elevation, direction, and duration.
  • A web app with shareable URLs, a pass table, a polar sky plot, a ground-track map, and the satellite's predicted position updating every second.
  • A documented JSON API (/api/passes) with interactive docs.
  • A command-line tool that also schedules contacts across several satellites and stations, dropping overlaps since one antenna can only track one satellite at a time.
  • An accuracy check against NASA's published ISS ephemeris.

03How it works

Propagation and pass finding

OrbitTrack downloads two-line elements (TLEs) from CelesTrak and propagates them with SGP4 through Skyfield. Instead of stepping through time, it uses Skyfield's event finder to locate each horizon-mask crossing to within half a second, then groups crossings into complete rise → peak → set passes.

Hardened for public traffic

Only six CelesTrak groups are accepted, because the group name becomes part of a cache file path and an outbound request, and free text could be used for path traversal or to spam CelesTrak. Every parameter is range-checked (bad values get a 422), the window is capped at 72 hours, visitor input is HTML-escaped, and all visitors share one TLE cache behind a lock so only one request re-downloads a stale file.

The production outage

After deploying, requests from Render's network to CelesTrak started timing out, so the live site couldn't get orbital data. I moved the download off the server: a GitHub Actions workflow fetches the six groups every 6 hours, checks each file actually contains TLEs, and publishes them to GitHub Pages, where the app reads them. If a download fails, the previous files stay up, and the app waits 5 minutes before retrying instead of hammering the source.

A live view without SGP4 in the browser

The page carries the server's SGP4 samples every 30 seconds and interpolates between them in east/north/up coordinates relative to the station, which stays smooth even straight overhead where azimuth can swing 180° in seconds. A test keeps the worst-case pointing error under 0.1°.

Testing it like it matters

135 offline tests run against a real CelesTrak TLE file and fixed start times, so results are deterministic. Web tests swap in the fixture with FastAPI dependency overrides, and CLI tests fail if anything tries to download. Pass predictions are cross-checked against independent brute-force sampling every 2 seconds over 48 hours: same passes, times within 3 seconds. CI runs pytest and mypy on every push.

OrbitTrack pass table with rise, peak, and set times next to a polar sky plot of each pass.
Upcoming ISS passes and a polar sky plot of each pass's path.
OrbitTrack ground-track map with passes over West Lafayette highlighted inside the station's coverage circle.
Ground track, with passes over the station highlighted inside its coverage circle.

04My role

  • Solo project: I designed, built, tested, and deployed it.

05What I'm working on

  • Bring multi-satellite contact scheduling from the CLI to the web app.
  • Compare more TLEs against successive NASA ephemerides to measure how accuracy falls off with TLE age.
  • Model atmospheric refraction, and let the scheduler weigh pass quality and priority.