OrbitScope is a real-time satellite tracker that works the moment you open the web page. Everything — parsing NORAD two-line element sets (TLE), SGP4 orbit propagation, coordinate transforms and map rendering — runs locally in your browser. There is no online map service, no backend, nothing to install, and it works fully offline, even with your network cable unplugged.
- 🧭 The problem it solves: most satellite trackers lean on online tile maps, a WebGL engine or a backend API. They are heavy, require a connection, and often upload your observing location. OrbitScope shrinks the whole experience into a single web page built on raw Canvas and hand-written orbital mechanics — small enough to run offline by double-clicking a file.
- ✨ What makes it different:
- A hand-written SGP4 propagator (near-earth branch, WGS-72 constants, TEME frame), checked point-by-point against the public-domain reference implementation — worst-case position error ≈ 0.7 mm, not an opaque library call;
- A bundled offline data pack: a TLE snapshot of 15 representative satellites (ISS, China's Tiangong, Hubble, Fengyun-3D, DAMPE, HXMT, …) plus 125 vector land polygons, so opening via
file://just works; - An observer-centric view: azimuth / elevation / slant range for any location, an overhead sky radar and 48-hour pass prediction (rise → culmination → set);
- A time machine: 1×–1800× speed, pause, single-step and "return to now", so you can watch orbital precession and the day/night terminator evolve.
- 💡 Inspiration: a trending 3D online-globe satellite visualizer inspired the goal of "letting anyone watch a satellite pass overhead". OrbitScope deliberately takes the opposite technical route — pure 2D, zero dependencies, offline-first — and adds the practical piece that project lacked: observer pass prediction. All orbital math is independently implemented from the public Space-Track Report #3 / Vallado public-domain algorithms; no third-party project code was copied.
- 🛰️ Hand-written SGP4 propagation — the complete near-earth branch (J2/J3/J4 perturbations, B* atmospheric drag), producing TEME position and velocity with sub-millimetre agreement versus the public-domain reference.
- 📡 Robust TLE parser — fixed-column decoding, compressed scientific-notation expansion, two-digit epoch-year rollover and per-line checksum validation; accepts 2-line/3-line formats and many satellites at once.
- 🌍 Complete coordinate pipeline — Julian date → GMST sidereal time → TEME→ECEF (including the Earth-rotation velocity term) → Bowring-iterated geodetic lat/lon/altitude → ENU topocentric azimuth / elevation / range.
- 🗺️ Dependency-free Canvas world map — bundled simplified vector coastlines, graticule, tropics/polar circles and a day/night terminator, seamlessly wrapped horizontally; drag to pan, scroll to zoom.
- 🛰️ Live positions, one-orbit ground tracks and coverage-footprint ellipses, colour-coded by station / science / weather / Earth-observation; visible satellites glow with a halo.
- 🧭 Observer panel — 10 city presets (defaults to Hefei), one-click browser geolocation and manual coordinates.
- 📡 Overhead sky radar — a polar plot showing every satellite currently above your horizon with its azimuth and altitude.
- 🌅 Pass prediction — for the selected satellite, predicts the next rise time, peak elevation and set time within 48 hours.
- ⏱️ Time engine — play/pause, 1×/10×/60×/300×/1800× speed, ±1-minute stepping and one-click return to the real current time.
- 📋 Satellite list — search by name or catalog number, a "visible only" filter and live sorting by current elevation.
- ➕ Custom TLE — paste any TLE to track it; optionally refresh online from Celestrak (automatically falls back to the bundled snapshot on failure).
- 🌐 One-click Chinese/English interface; documentation in Simplified Chinese, Traditional Chinese and English.
- 🔒 Privacy-friendly — all computation stays on your machine; by default no network request is ever made.
| Item | Requirement |
|---|---|
| Runtime | Any modern browser (recent Chrome / Edge / Firefox / Safari) |
| Node.js | Only needed for tests or the local server, ≥ 16 recommended (not needed to run the app) |
| Network | ❌ Not required — fully offline by default |
| Package install | ❌ Zero runtime npm dependencies — no npm install needed |
After downloading or cloning, double-click index.html to open it in your browser. The data is bundled, so it works with no connection.
git clone https://github.com/gitstq/orbitscope.git
# then open orbitscope/index.html in a browsercd orbitscope
npm run serve # equivalent to: node tools/serve.mjs (port 8080)
# open http://localhost:8080Pick a different port:
node tools/serve.mjs 3000Why ship a server at all? A few browsers restrict ES Modules over
file://. A local server guarantees a consistent experience. No bundler is involved.
npm testThis runs the SGP4 numerical-conformance suite, the coordinate-math suite and an end-to-end pipeline check over every bundled satellite. Expected output:
SGP4 conformance: 20 points checked, 0 failures
worst position error: 7.143e-7 km (tol 0.001)
coords: 29 checks, 0 failures
pipeline: 15 satellites validated, 0 failures
PASS
- Choose a city preset in the "Observer location" panel, or enter decimal degrees manually in
Lat / Lon(north/east positive). - "Locate" calls the browser geolocation API (used locally only, never uploaded).
- The white bullseye on the map marks the observer; the sky radar, elevation values and pass predictions are all relative to it.
- Option A: click a satellite dot directly on the map.
- Option B: click an entry in the satellite list; search by name or NORAD catalog number, and tick "Visible only" to hide everything below the horizon.
- Once selected you get a gold dashed ground track (half an orbit each way), a dotted coverage-footprint ellipse and a detail panel (lat/lon, altitude, speed, period, inclination, azimuth/elevation/range to you, and the next pass).
| Control | Effect |
|---|---|
| ⏸ / ▶ | Pause / resume the simulated clock |
| « / » | Step back / forward by 1 minute |
| 1×–1800× | Time multiplier (at 1800×, one real second ≈ 30 simulated minutes) |
| Now | Jump the simulation back to the real current time |
At high speed you can clearly see each orbit drift westward (precession + Earth rotation) and watch satellites cross the day/night terminator.
- The radar is polar: north (N) is at the top, rings map elevation 0°→90° from edge to centre, and the centre is your zenith.
- Only satellites with elevation > 0° (above the horizon) appear; the header shows how many are visible now.
- "Next pass" shows
rise → culmination (elevation) → setin local time; if no pass occurs within 48 h you are told so explicitly.
Click "+ Custom TLE" in the top-right and paste a 2-line or 3-line element set (several at once is fine):
ISS (ZARYA)
1 25544U 98067A 26242.62751382 .00005530 00000+0 10868-3 0 9998
2 25544 51.6315 291.6521 0005038 90.5345 269.6220 15.48940460583296
- Targets with bad checksums, or deep-space objects (period ≥ 225 min, e.g. geostationary), are skipped automatically.
- "Update online" fetches the stations / science / weather groups from Celestrak to replace the bundled snapshot; when offline it keeps the bundled data and tells you.
The orbital-math layer is fully decoupled from the UI and reusable in the browser or in Node (ESM):
import { parseTleText } from './src/tle.js';
import { initSatellite, propagate, minutesSinceEpoch } from './src/sgp4.js';
import { julianDate, gmst, temeToEcef, ecefToGeodetic } from './src/coords.js';
const [el] = parseTleText(TLE_STRING);
const sat = initSatellite(el);
const when = new Date();
const { position, velocity } = propagate(sat, minutesSinceEpoch(sat, when)); // TEME, km / km·s⁻¹
const gst = gmst(julianDate(when));
const { lat, lon, alt } = ecefToGeodetic(temeToEcef(position, gst)); // geodetic
console.log(lat.toFixed(2), lon.toFixed(2), alt.toFixed(1));Drop a screenshot/GIF here (suggested path
assets/screenshot.png): the world-map view, the sky radar and the custom-TLE dialog.
- Hand-write SGP4 instead of importing a library? Zero dependencies and auditability come first. Orbital propagation is the heart of the product; writing it ourselves means owning every perturbation step and locking it with sub-millimetre regression against a public-domain reference — no opaque library or version drift.
- 2D Canvas instead of a 3D globe? An equirectangular projection expresses ground tracks, coverage and the terminator with the simplest possible math, renders smoothly as a single file, and runs on old or GPU-less devices. A 3D mode is an optional future enhancement.
- Bundle the data? TLEs and coastlines are tiny (a few KB snapshot, ~68 KB simplified land). Bundling is what makes "double-click, works offline" true; online refresh is an enhancement, not a requirement.
- Reference frames: propagation uses the historic WGS-72 constants (as SGP4 is defined); the display-layer geodetic conversion uses WGS-84 — the standard industry combination.
TLE text ──► tle.js parse/validate ──► sgp4.js propagation (TEME)
│
▼
coords.js: GMST → ECEF → geodetic → ENU look-angles
│
┌───────────────────────────────┼───────────────────────────────┐
▼ ▼ ▼
app.js world map sky radar / pass prediction satellite list / details
- Deep-space SGP4 branch (period ≥ 225 min) for geostationary / Molniya orbits
- Visual-magnitude (stellar magnitude) estimates and "naked-eye friendly" flags
- Manage and compare multiple observer ground stations
- Optional WebGL 3D globe as progressive enhancement (the zero-dependency 2D path stays intact)
- Favourite satellites / observers with localStorage persistence
- More bundled groups (navigation constellations, Starlink batches) and more languages (JA / KO / ES)
New city presets, TLE edge cases, coordinate/SGP4 regression vectors, accessibility improvements and translations are all welcome — see CONTRIBUTING.md.
OrbitScope is a pure front-end static tool (not a compiled executable), so no platform binaries are released — any static host works.
The folder itself is the artifact: copy index.html, styles.css and src/ anywhere. There is no build step.
- GitHub Pages: Settings → Pages → select the root of the
mainbranch. No build command; output directory/. - Nginx: place the folder under
root; make sure.jsis served asContent-Type: text/javascript(default on modern Nginx). - Any object storage: upload all files with read-only access and set
index.htmlas the default document.
docker run --rm -p 8080:80 -v "$PWD":/usr/share/nginx/html:ro nginx:alpine
# open http://localhost:8080- Recommended: Chrome / Edge / Firefox / Safari from the last ~2 years (ES2020 + Canvas 2D required).
- ES Modules are used, so open via a modern browser or a same-origin static server.
- Below 980 px viewport width the layout automatically stacks vertically for tablets/phones.
Q: Why is a satellite a few kilometres off compared to another site? A: Almost always a stale TLE. A TLE is a fitted initial condition and drifts noticeably after ~2 weeks — refresh online or paste a newer one. The detail panel flags epochs older than 14 days.
Q: Why can't I add a geostationary satellite? A: The current build focuses on near-earth orbits (period < 225 min); the deep-space branch is on the roadmap. Such objects are skipped safely rather than computed incorrectly.
Q: Does it need a network or upload my location? A: It is offline with zero network requests by default; your observer location lives only in page memory. Only when you actively press "Update online" / "Locate" does it contact Celestrak / the browser geolocation service.
Q: A blank screen when opened via file://?
A: A few browsers restrict local ES Modules — use npm run serve or any static server instead.
Issues, PRs and translations are welcome! Read CONTRIBUTING.md first. Commit messages follow the Angular convention:
feat: a new feature
fix: a bug fix
docs: documentation only
refactor: refactor with no behaviour change
test: tests
chore: build / tooling
Hard rule: after touching orbital math, npm test must stay green, and no runtime third-party dependency may be introduced (the zero-dependency promise stands).
Released under the MIT License — free to use, modify and commercialise, provided the copyright and permission notice is retained.
Third-party data / theory credits:
- Land outline derived from
world-atlas(BSD-3-Clause); underlying geometry from Natural Earth (public domain). - Bundled TLE data from Celestrak (redistributing US Space Force 18 SDS element sets).
- SGP4 theory is the public-domain NORAD standard (Space-Track Report #3; Vallado et al., 2006); the code is an independent hand implementation.
⭐ If OrbitScope is useful to you, a Star helps others find the satellites passing overhead too.