Complete pass prediction with look angles and Doppler shift at each moment of the pass. Useful for ground station automation, antenna pointing, and frequency compensation.
Setup
# Fetch a satellite
{:ok, [sat]} = Orbis.CelesTrak.fetch_tle(25544)
# Define your ground station
station = %{latitude: 51.5074, longitude: -0.1278, altitude_m: 11.0} # LondonFind Passes
start = DateTime.utc_now()
stop = DateTime.add(start, 86400, :second)
passes = Orbis.Passes.predict(sat, station, start, stop, min_elevation: 5.0)
IO.puts("Found #{length(passes)} passes above 5°")Detailed Pass Report
For each pass, sample the trajectory at 10-second intervals:
for pass <- Enum.take(passes, 3) do
IO.puts("\n--- Pass: #{pass.rise} to #{pass.set} (max el: #{Float.round(pass.max_elevation, 1)}°) ---")
IO.puts("Time | Az | El | Range km | Doppler Hz")
IO.puts(String.duplicate("-", 75))
duration = DateTime.diff(pass.set, pass.rise)
steps = div(duration, 10)
for i <- 0..steps do
dt = DateTime.add(pass.rise, i * 10, :second)
case Orbis.look_angle(sat, dt, station) do
{:ok, look} when look.elevation > 0 ->
# Doppler at 437 MHz (UHF amateur)
{:ok, doppler} = Orbis.doppler(sat, dt, station, 437.0e6)
IO.puts(
"#{dt} | #{String.pad_leading(Float.to_string(Float.round(look.azimuth, 1)), 6)} " <>
"| #{String.pad_leading(Float.to_string(Float.round(look.elevation, 1)), 5)} " <>
"| #{String.pad_leading(Float.to_string(Float.round(look.range_km, 0)), 8)} " <>
"| #{Float.round(doppler.doppler_shift_hz, 0)}"
)
_ ->
:skip
end
end
endEclipse-Aware Passes
Check whether the satellite is sunlit during a pass (important for optical observations):
# Requires a JPL ephemeris file for Sun position
# Download de421.bsp from https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/
eph = Orbis.Ephemeris.load("/path/to/de421.bsp")
for pass <- passes do
{:ok, status} = Orbis.Eclipse.check(sat, pass.max_elevation_time, eph)
case status do
:sunlit -> IO.puts("#{pass.rise}: VISIBLE (sunlit, max el #{Float.round(pass.max_elevation, 1)}°)")
:penumbra -> IO.puts("#{pass.rise}: partial shadow")
:umbra -> IO.puts("#{pass.rise}: in Earth's shadow")
end
end