sidereon

GNSS and astrodynamics for Elixir: propagate satellites, predict passes, solve precise positions (SPP / RTK / PPP), and convert between coordinate frames and time scales — checked against the references the field trusts (Vallado, Skyfield, IGS, IERS).

The numerics run in a Rust engine that ships as a precompiled NIF — adding the dependency downloads a prebuilt binary for your platform, so there's no Rust toolchain to install and nothing to compile. You write ordinary Elixir.

Run in Livebook

Install

def deps do
[{:sidereon, "~> 0.8"}]
end

Published on Hex. Releases ship precompiled NIFs for common Linux, macOS, and Windows targets and download automatically — no Rust build. (Set SIDEREON_BUILD=1 to compile from source instead.)

Quickstart: when does the ISS fly over you?

No data files, no setup — give it a two-line element set and a ground station, and ask when the satellite is above the horizon.

# Real orbital elements for the ISS (grab fresh ones from CelesTrak any time).
{:ok, iss} =
Sidereon.parse_tle(
"1 25544U 98067A 26178.50947090 .00006280 00000+0 12016-3 0 9996",
"2 25544 51.6322 248.9966 0004278 238.4942 121.5629 15.49454046573359"
)
# A ground station: latitude/longitude in degrees, altitude in meters.
berkeley = %{latitude: 37.87, longitude: -122.27, altitude_m: 0.0}
# Every pass that peaks above 10° over the next 24 hours.
now = DateTime.utc_now()
{:ok, passes} =
Sidereon.predict_passes(iss, berkeley, now, DateTime.add(now, 1, :day),
min_elevation: 10.0
)
for p <- passes do
rise = Calendar.strftime(p.rise, "%H:%M")
mins = Float.round(p.duration_seconds / 60, 1)
IO.puts("#{rise} UTC · #{mins} min · peak #{round(p.max_elevation)}°")
end
08:28 UTC · 10.9 min · peak 88°
10:06 UTC · 9.7 min · peak 16°
13:22 UTC · 9.3 min · peak 13°
14:59 UTC · 10.9 min · peak 56°
16:36 UTC · 9.2 min · peak 14°

Each %Sidereon.Pass{} carries rise, set, max_elevation, max_elevation_time, and duration_seconds. The same parsed elements feed Sidereon.propagate/2 (TEME position/velocity), Sidereon.geodetic/2 (the sub-satellite point), and Sidereon.look_angle/3 (azimuth/elevation/range from a station). Times are plain DateTime structs in, DateTime structs out.

Precise positioning

The positioning engine is the other half of the library: feed it pseudoranges and a precise-ephemeris product and it returns a least-squares fix.

{:ok, sp3} = Sidereon.GNSS.SP3.load("igs_product.sp3")
observations = [{"G07", 24_602_022.18}, {"G08", 23_676_569.52}, {"E05", 27_038_058.35}]
{:ok, sol} =
Sidereon.GNSS.Positioning.solve(sp3, observations, ~N[2020-06-24 12:00:00],
ionosphere: true,
troposphere: true
)
sol.position # %{x_m: ..., y_m: ..., z_m: ...} — ITRF/IGS ECEF meters
sol.geodetic # %{lat_rad: ..., lon_rad: ..., height_m: ...}
sol.dop.pdop # position dilution of precision
sol.used_sats # satellites that contributed to the fix

Sidereon.GNSS.RTK and the PPP solvers follow the same shape — observations and a product in, a typed solution with ECEF/geodetic positions and geometry diagnostics out. Need the products? Sidereon.GNSS.Data fetches and caches SP3, RINEX, and IONEX from the public archives.

What's in the box

Elixir is sidereon's broadest interface — the full engine is exposed:

Every result is exactly what the engine computes, returned as plain Elixir structs and maps with {:ok, _} / {:error, _} tuples. Full signatures live on HexDocs, with runnable Livebooks under examples/.

Other languages

sidereon is one validated engine with first-class interfaces in Rust, Python, C, Elixir, and WebAssembly — same numbers everywhere. See the live demo and docs at sidereon.dev.

How it's validated

The SGP4 propagator is a Rust port of David Vallado's reference implementation, bit-exact to it. Frames and time are checked against Skyfield and IERS; the positioning stack is checked against IGS products. Detailed agreement results are in guides/accuracy.md.

License

MIT. The engine's SGP4 propagation is a Rust port of David Vallado's reference implementation (credit: David Vallado, AIAA 2006); see the sidereon-core crate for full attribution.