Track the ISS from a Ground Station

Copy Markdown View Source

This guide walks through fetching the ISS TLE, propagating its orbit, computing look angles, and setting up real-time tracking — all in about 20 lines of Elixir.

Fetch the TLE

{:ok, [iss]} = Orbis.CelesTrak.fetch_tle(25544)

Propagate to Now

{:ok, teme} = Orbis.propagate(iss, DateTime.utc_now())
IO.inspect(teme.position, label: "TEME position (km)")

Get Geodetic Coordinates

{:ok, geo} = Orbis.geodetic(iss, DateTime.utc_now())
IO.puts("ISS is at #{Float.round(geo.latitude, 2)}°N, #{Float.round(geo.longitude, 2)}°E")
IO.puts("Altitude: #{Float.round(geo.altitude_km, 1)} km")

Look Angles from Your Location

# New York City
station = %{latitude: 40.7128, longitude: -74.006, altitude_m: 10.0}

{:ok, look} = Orbis.look_angle(iss, DateTime.utc_now(), station)
IO.puts("Azimuth: #{Float.round(look.azimuth, 1)}°")
IO.puts("Elevation: #{Float.round(look.elevation, 1)}°")
IO.puts("Range: #{Float.round(look.range_km, 1)} km")

if look.elevation > 0 do
  IO.puts("ISS is above the horizon!")
else
  IO.puts("ISS is below the horizon.")
end

Real-Time Tracking

Start a tracker that updates every second:

{:ok, tracker} = Orbis.Tracker.start_link(iss, interval_ms: 1000)
Orbis.Tracker.subscribe(tracker)

# Receive 5 updates
for _ <- 1..5 do
  receive do
    {:orbis_tracker, _pid, state} ->
      geo = state.geodetic
      IO.puts("#{state.time} | #{Float.round(geo.latitude, 2)}°N, #{Float.round(geo.longitude, 2)}°E | #{Float.round(geo.altitude_km, 1)} km")
  end
end

Orbis.Tracker.stop(tracker)

Predict Passes

Find when the ISS will be visible from your location:

now = DateTime.utc_now()
tomorrow = DateTime.add(now, 86400, :second)

passes = Orbis.Passes.predict(iss, station, now, tomorrow, min_elevation: 10.0)

for pass <- passes do
  duration = DateTime.diff(pass.set, pass.rise)
  IO.puts("Rise: #{pass.rise} | Max el: #{Float.round(pass.max_elevation, 1)}° | Duration: #{duration}s")
end

Compute path loss for a UHF amateur radio link (437 MHz):

{:ok, look} = Orbis.look_angle(iss, DateTime.utc_now(), station)

if look.elevation > 0 do
  fspl = Orbis.RF.fspl(look.range_km, 437.0)
  IO.puts("Free-space path loss: #{Float.round(fspl, 1)} dB")

  margin = Orbis.RF.link_margin(%{
    eirp_dbw: Orbis.RF.eirp(30.0, 10.0),  # 1W + 10 dBi Yagi
    fspl_db: fspl,
    receiver_gt_dbk: -20.0,
    other_losses_db: 3.0,
    required_cn0_dbhz: 30.0
  })
  IO.puts("Link margin: #{Float.round(margin, 1)} dB")
end