sidereon

Hex.pmHexDocsRun in Livebook

GNSS and astrodynamics for Elixir: propagate satellites, predict passes, solve precise positions (SPP / RTK / PPP / DGNSS), screen for conjunctions, and convert between coordinate frames and time scales.

This is the Elixir interface to sidereon, a GNSS and astrodynamics engine written in Rust. The numerics run in that engine and ship to you as a Rustlerprecompiled NIF: adding the dependency downloads a prebuilt binary for your platform, so there is no Rust toolchain to install and nothing to compile. You write ordinary Elixir, with plain DateTime and map structures in and typed structs out.

The engine is reference-validated. The SGP4 propagator is a 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.

Install

Add :sidereon to your dependencies in mix.exs:

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

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

Example: track a satellite

Parse a two-line element set, run SGP4, and take a look angle from a ground station. No data files and no setup: give it the elements and a station, and it returns azimuth, elevation, and slant range.

line1 = "1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9009"
line2 = "2 25544 51.6400 208.8657 0002644 250.3037 109.7782 15.49560812999990"
station = %{latitude: 51.5, longitude: -0.1, altitude_m: 10.0}
with {:ok, tle} <- Sidereon.parse_tle(line1, line2),
{:ok, look} <- Sidereon.look_angle(tle, ~U[2024-01-01 12:00:00Z], station) do
look.azimuth # degrees
look.elevation # degrees
look.range_km # slant range
end

The same parsed elements feed Sidereon.propagate/2 (TEME position and velocity), Sidereon.geodetic/2 (the sub-satellite point), and Sidereon.predict_passes/5 (every pass above a minimum elevation over a window).

Example: solve a position

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 with ECEF and geodetic positions plus geometry diagnostics.

# GPS L1 pseudoranges (meters) for the satellites in view at the epoch.
observations = [
{"G08", 23_825_519.8},
{"G10", 22_717_690.1},
{"G16", 20_478_653.4},
{"G18", 21_768_335.2},
{"G20", 21_248_327.7},
{"G21", 20_808_709.8}
]
# `sp3_data` is a precise SP3 ephemeris (a string, or load one with
# `Sidereon.GNSS.SP3.load/1`).
with {:ok, sp3} <- Sidereon.GNSS.SP3.parse(sp3_data),
{:ok, solution} <-
Sidereon.GNSS.Positioning.solve(sp3, observations, ~N[2020-06-24 12:00:00],
initial_guess: [4_500_000.0, 500_000.0, 4_500_000.0, 0.0]) do
solution.position # %{x_m, y_m, z_m} ITRF/IGS ECEF meters
solution.geodetic # %{lat_rad, lon_rad, height_m}
solution.rx_clock_s # receiver clock bias, seconds
solution.dop.pdop # position dilution of precision
solution.used_sats # satellites that contributed to the fix
end

Sidereon.GNSS.RTK and the PPP and DGNSS solvers follow the same shape: observations and a product in, a typed solution out.

A runnable sidereon.livemd walks through propagation, positioning, and conjunction screening; more notebooks live under examples/.

What's in the box

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

Other languages

sidereon is one validated engine with first-class interfaces in several languages: Rust (sidereon), Python (sidereon-python), C (sidereon-c), Elixir (this package), and WebAssembly (sidereon-wasm). The same numbers come out everywhere. See the live demo and docs at sidereon.dev.

License

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