I’ve heard the ISS repeater. Tuned to 437.800 MHz, heard voices coming through — it’s busy up there. I haven’t tried to make a contact yet, but listening to people bounce signals off something travelling at 28,000 km/h overhead is hard to ignore. That’s what got me interested in satellite repeaters, and why I wanted to build a tracker specifically for amateur radio operators.

Most satellite trackers show you where the ISS is. That’s fine, but as a radio operator I need more: what frequency, what mode, do I need a CTCSS tone, when’s the next pass over my location, and how high will it get? So I built one that answers those questions for eight popular amateur satellites.

The data — TLE and SGP4

Satellite positions aren’t fetched from a live GPS feed. They’re calculated from Two-Line Element sets (TLEs) — a compact format that describes a satellite’s orbit using six Keplerian elements plus drag terms. CelesTrak publishes TLEs for every catalogued object, updated several times a day, with no authentication required.

A TLE looks like this:

ISS (ZARYA)
1 25544U 98067A   26100.50000000  .00016717  00000-0  10270-3 0  9002
2 25544  51.6400 200.0000 0006000  90.0000 270.0000 15.54900000000000

Two lines of numbers encoding the orbit shape, orientation, and timing. Fresh TLEs are accurate to within a few kilometres. After about two days they start drifting, which is why the tracker shows TLE age — anything under two days is reliable.

To turn a TLE into a latitude/longitude at a specific time, you need SGP4 — the Simplified General Perturbations model. It accounts for Earth’s oblateness, atmospheric drag, and gravitational harmonics. The maths is well-understood but tedious to implement from scratch. I used satellite.js, which implements the full SGP4/SDP4 propagator in JavaScript and runs comfortably in the browser.

The flow is straightforward: fetch TLE from CelesTrak, parse it into a satrec object, then call propagate(satrec, date) for any point in time to get the satellite’s position in Earth-Centered Inertial coordinates. Convert that to geodetic (lat/lon/alt) and you’ve got a map position.

The map

I used Leaflet with CartoDB Dark Matter tiles. The dark theme fits the site aesthetic, and Leaflet is lightweight, well-documented, and doesn’t need an API key.

The satellite position is a circle marker — an amber dot that updates every second. The orbit path is drawn as a polyline by propagating the position at one-minute intervals across a full orbital period (about 90 minutes for LEO satellites). One thing that caught me out: the orbit path crosses the antimeridian (the date line at ±180° longitude), and if you draw a single polyline through that crossing, Leaflet draws a line straight across the map. The fix is to detect longitude jumps greater than 180° between consecutive points and split the path into separate polyline segments.

Pass predictions

This is the part that makes the tracker actually useful for operating. If you enter your grid locator or coordinates, the tracker scans the next 72 hours in 10-second steps, computing the satellite’s elevation angle from your location at each step. Any continuous period where the satellite is above the horizon (elevation > 0°) is a pass.

For each pass, the tracker records the start time, end time, maximum elevation, and the azimuth at rise, peak, and set. Maximum elevation matters — a pass that barely scrapes 5° above the horizon is technically visible but probably not workable through trees and buildings. Passes above 30° are flagged as good.

The prediction algorithm uses satellite.ecfToLookAngles() from satellite.js, which takes the observer’s geodetic position and the satellite’s ECI position and returns azimuth, elevation, and range. The 10-second step size is a compromise between accuracy and computation time — scanning 72 hours at one-second resolution would take too long in the browser.

Eight satellites, not just the ISS

The tracker supports eight satellites that are commonly used by amateur operators:

FM repeaters — ISS (NA1SS), SO-50, and AO-91. These are the most accessible for beginners. A handheld with a decent antenna can work them. All three use a 67.0 Hz CTCSS tone on the uplink.

Digipeater — IO-117 (GREENCUBE). This one operates as a packet digipeater on 435.310 MHz. Different operating style to the FM birds — you’re sending digital packets rather than voice.

Linear transponders — AO-73, JO-97, FO-29, and AO-7. These retransmit a slice of spectrum rather than a single channel, so you use SSB or CW. More challenging to work — you need to account for Doppler shift and keep your signal within the transponder passband — but they’re fascinating.

Each satellite in the tracker shows its mode, uplink and downlink frequencies, and CTCSS tone where applicable. When you switch satellites, the TLE is fetched (and cached so switching back is instant), the map updates with the new position and orbit, and the frequency info updates.

Keeping it client-side

Everything runs in the browser. No backend, no accounts, no API keys. CelesTrak’s TLE API is open and CORS-enabled. satellite.js and Leaflet both load from CDNs. Your location stays in localStorage on your machine.

This was a deliberate choice. The tool should work offline after the first load (TLEs are cached), and there’s no reason to route orbital mechanics through a server when the browser handles it fine. The entire page is a single Astro file with inline JavaScript.

What’s next

I want to add Doppler shift prediction — showing the corrected receive frequency as a satellite approaches and recedes. For linear transponder satellites this is essential, and even for FM birds it helps to know which direction you’ll be offset. I’m also looking at adding visual pass tracks on the map, drawing the ground track of upcoming passes so you can see the geometry of the flyover.

But for now, the tracker does what I need: tells me which satellites are up, when they’re coming over, and what frequency to tune to.

Try it

The tracker is live at skipzone.co.uk/tools/iss-tracker. Pick a satellite, enter your location, and see what’s coming over next.

73 de MM7IUY