Sidereon (Sidereon v0.19.1)

Copy Markdown View Source

Satellite toolkit for Elixir. SGP4 orbit propagation, coordinate transformations, and ground station pass prediction.

Summary

Functions

Compute Doppler shift for a satellite-ground link.

Check whether a satellite is in Earth's shadow (eclipse) at a given time.

Solve the WGS84 direct geodesic problem.

Solve the WGS84 inverse geodesic problem.

Compute geodetic coordinates (lat/lon/alt) for a satellite at a given time.

Compute the ground track (sub-satellite points) for a satellite over a list of times.

Compute the look angle (azimuth/elevation/range) from a ground station to a satellite at a given time.

Compute the angle between satellite nadir and the Moon direction.

Compute ocean tide loading station displacement in metres, ECEF.

Parse a Two-Line Element set.

Parse a multi-record TLE file (CelesTrak / Space-Track style) into named satellites.

Predict visible passes of a satellite over a ground station.

Propagate orbital elements to a specific datetime, returning TEME position and velocity.

Compute solid-earth pole tide station displacement in metres, ECEF.

Compute solid-earth tide station displacement in metres, ECEF.

Compute the angle between satellite nadir and the Sun direction.

Compute Sun and Moon ECEF vectors for UTC Unix microsecond epochs.

Compute Sun and Moon ECI vectors for UTC Unix microsecond epochs.

Convert a TEME state vector to GCRS (Geocentric Celestial Reference System).

Fixed inter-system time offset to - from, in seconds, for the atomic scales.

Leap-aware inter-system time offset to - from, in seconds, at utc_jd.

Types

gcrs_state()

@type gcrs_state() :: %{position: vec3(), velocity: vec3()}

ground_station()

@type ground_station() :: %{
  latitude: number(),
  longitude: number(),
  altitude_m: number()
}

vec3()

@type vec3() :: {number(), number(), number()}

Functions

bounded_ils_search(float_cycles, covariance, radius \\ 1, candidate_limit \\ 200_000, ratio_threshold \\ 3.0)

See Sidereon.ILS.bounded_ils_search/5.

decode_rtcm(data)

See Sidereon.GNSS.RTCM.decode/1.

decode_rtcm_frame(frame)

See Sidereon.GNSS.RTCM.decode_frame/1.

decode_rtcm_message(body)

See Sidereon.GNSS.RTCM.decode_message/1.

decode_rtcm_stream(data)

See Sidereon.GNSS.RTCM.decode_stream/1.

doppler(tle, datetime, station, frequency_hz)

@spec doppler(Sidereon.Elements.t(), DateTime.t(), ground_station(), number()) ::
  {:ok,
   %{range_rate_km_s: float(), doppler_hz: float(), doppler_ratio: float()}}
  | {:error, term()}

Compute Doppler shift for a satellite-ground link.

Propagates the TLE, transforms to GCRS, and computes the range rate and Doppler shift at the given carrier frequency.

The station is a map: %{latitude: deg, longitude: deg, altitude_m: meters}.

Returns {:ok, %{range_rate_km_s: float, doppler_hz: float, doppler_ratio: float}}.

Example

station = %{latitude: 40.0, longitude: -74.0, altitude_m: 0.0}
{:ok, d} = Sidereon.doppler(tle, datetime, station, 437.0e6)
d.doppler_hz  # => ~10_000.0

eclipse(tle, datetime, ephemeris)

@spec eclipse(Sidereon.Elements.t(), DateTime.t(), Sidereon.Ephemeris.t()) ::
  {:ok, :sunlit | :penumbra | :umbra} | {:error, term()}

Check whether a satellite is in Earth's shadow (eclipse) at a given time.

Propagates the TLE, transforms to GCRS, fetches the Sun position from the ephemeris, and returns the eclipse status.

Returns {:ok, :sunlit | :penumbra | :umbra} or {:error, reason}.

Example

{:ok, eph} = Sidereon.Ephemeris.load("de421.bsp")
{:ok, status} = Sidereon.eclipse(tle, datetime, eph)

encode_rtcm(message)

See Sidereon.GNSS.RTCM.encode/1.

encode_rtcm_frame(message_or_body)

See Sidereon.GNSS.RTCM.encode_frame/1.

find_tca_candidates(primary_line1, primary_line2, secondary_line1, secondary_line2, window_start_jd, window_end_jd, opts \\ [])

See Sidereon.Conjunction.find_tca_candidates/7.

find_tca_conjunctions(primary_line1, primary_line2, secondary_line1, secondary_line2, window_start_jd, window_end_jd, hard_body_radius_km, opts \\ [])

See Sidereon.Conjunction.find_tca_conjunctions/8.

from_celestrak_json(json, system \\ :gps)

See Sidereon.GNSS.Constellation.from_celestrak_json/2.

from_celestrak_json_lenient(json, system \\ :gps)

See Sidereon.GNSS.Constellation.from_celestrak_json_lenient/2.

from_celestrak_omm_lenient(json, system \\ :gps)

See Sidereon.GNSS.Constellation.from_celestrak_omm_lenient/2.

geodesic_direct(lat1_deg, lon1_deg, azi1_deg, s12_m)

@spec geodesic_direct(number(), number(), number(), number()) ::
  {:ok, Sidereon.Geodesic.direct_result()}
  | {:error, Sidereon.Geodesic.GeodesicError.t()}

Solve the WGS84 direct geodesic problem.

See Sidereon.Geodesic.direct/4 for details.

geodesic_inverse(lat1_deg, lon1_deg, lat2_deg, lon2_deg)

@spec geodesic_inverse(number(), number(), number(), number()) ::
  {:ok, Sidereon.Geodesic.inverse_result()}
  | {:error, Sidereon.Geodesic.GeodesicError.t()}

Solve the WGS84 inverse geodesic problem.

See Sidereon.Geodesic.inverse/4 for details.

geodetic(tle, datetime)

@spec geodetic(Sidereon.Elements.t(), DateTime.t()) ::
  {:ok, Sidereon.Geodetic.t()} | {:error, term()}

Compute geodetic coordinates (lat/lon/alt) for a satellite at a given time.

Propagates the TLE, transforms TEME -> GCRS -> ITRS, and converts to WGS84.

Returns {:ok, %{latitude: deg, longitude: deg, altitude_km: km}}.

Example

{:ok, tle} = Sidereon.parse_tle(line1, line2)
{:ok, geo} = Sidereon.geodetic(tle, datetime)
geo.latitude  # => 51.23

ground_track(tle, datetimes, opts \\ [])

@spec ground_track(Sidereon.Elements.t(), [DateTime.t()], keyword()) ::
  {:ok, [Sidereon.Geodetic.t()]} | {:error, term()}

Compute the ground track (sub-satellite points) for a satellite over a list of times.

For each datetime the satellite is propagated and reduced through TEME -> GCRS -> ITRS -> WGS84 geodetic, yielding the point on the ellipsoid directly beneath the satellite. This is the batch companion to Sidereon.geodetic/2.

Options

Returns {:ok, [%Sidereon.Geodetic{}]} (one per datetime, in order) or {:error, reason}.

Example

now = DateTime.utc_now()
times = for s <- 0..600//60, do: DateTime.add(now, s, :second)
{:ok, track} = Sidereon.ground_track(tle, times)
hd(track).latitude  # => 12.34

lambda_ils_search(float_cycles, covariance, ratio_threshold \\ 3.0)

See Sidereon.ILS.lambda_ils_search/3.

load_rinex_clock(path)

See Sidereon.GNSS.RINEX.Clock.load/1.

load_rinex_clock_lossy(path)

See Sidereon.GNSS.RINEX.Clock.load_lossy/1.

look_angle(tle, datetime, station, opts \\ [])

@spec look_angle(Sidereon.Elements.t(), DateTime.t(), ground_station(), keyword()) ::
  {:ok, Sidereon.LookAngle.t()} | {:error, term()}

Compute the look angle (azimuth/elevation/range) from a ground station to a satellite at a given time.

The station is a map: %{latitude: deg, longitude: deg, altitude_m: meters}.

Returns {:ok, %{azimuth: deg, elevation: deg, range_km: km}}.

Options

  • :opsmode - SGP4 operation mode, :afspc (default) or :improved. The satellite is built with this opsmode, so the look angle is consistent with Sidereon.predict_passes/5 run under the same opsmode.

Example

station = %{latitude: 40.0, longitude: -74.0, altitude_m: 0.0}
{:ok, look} = Sidereon.look_angle(tle, datetime, station)
look.elevation  # => 25.7

moon_angle(satellite_gcrs_position, moon_position_from_earth)

@spec moon_angle(vec3(), vec3()) :: float()

Compute the angle between satellite nadir and the Moon direction.

See Sidereon.Angles.moon_angle/2 for details.

ocean_tide_loading(station_ecef_m, year, month, day, fhr, amplitude_m, phase_deg)

@spec ocean_tide_loading(
  vec3(),
  integer(),
  integer(),
  integer(),
  number(),
  [[number()]],
  [[number()]]
) ::
  {:ok, vec3()} | {:error, term()}

Compute ocean tide loading station displacement in metres, ECEF.

parse_rinex_clock(text)

See Sidereon.GNSS.RINEX.Clock.parse/1.

parse_rinex_clock_lossy(text)

See Sidereon.GNSS.RINEX.Clock.parse_lossy/1.

parse_tle(line1, line2)

@spec parse_tle(String.t(), String.t()) ::
  {:ok, Sidereon.Elements.t()} | {:error, String.t()}

Parse a Two-Line Element set.

Returns {:ok, %Sidereon.Elements{}} or {:error, reason}.

Examples

iex> {:ok, el} = Sidereon.parse_tle(
...>   "1 25544U 98067A   18184.80969102  .00001614  00000-0  31745-4 0  9993",
...>   "2 25544  51.6414 295.8524 0003435 262.6267 204.2868 15.54005638121106"
...> )
iex> el.catalog_number
"25544"

parse_tle_file(text)

@spec parse_tle_file(String.t()) ::
  {:ok,
   %{
     satellites: [%{name: String.t(), tle: Sidereon.Elements.t()}],
     skipped: non_neg_integer()
   }}

Parse a multi-record TLE file (CelesTrak / Space-Track style) into named satellites.

Handles bare two-line sets, three-line name+line1+line2 sets, and CelesTrak 0 NAME markers, tolerating blank lines and CRLF. Returns {:ok, %{satellites: [%{name: name, tle: %Sidereon.Elements{}}], skipped: n}}, where each tle is ready for propagate/2, look_angle/3, etc., and skipped counts records that failed SGP4 initialization.

Examples

iex> text = """
...> ISS (ZARYA)
...> 1 25544U 98067A   18184.80969102  .00001614  00000-0  31745-4 0  9993
...> 2 25544  51.6414 295.8524 0003435 262.6267 204.2868 15.54005638121106
...> """
iex> {:ok, %{satellites: [sat], skipped: 0}} = Sidereon.parse_tle_file(text)
iex> sat.name
"ISS (ZARYA)"

predict_passes(tle, ground_station, start_time, end_time, opts \\ [])

Predict visible passes of a satellite over a ground station.

See Sidereon.Passes.predict/5 for full documentation.

propagate(tle, datetime)

Propagate orbital elements to a specific datetime, returning TEME position and velocity.

Returns {:ok, %Sidereon.TemeState{}} with position in km and velocity in km/s, or {:error, reason}.

Examples

iex> {:ok, el} = Sidereon.parse_tle(
...>   "1 25544U 98067A   18184.80969102  .00001614  00000-0  31745-4 0  9993",
...>   "2 25544  51.6414 295.8524 0003435 262.6267 204.2868 15.54005638121106"
...> )
iex> {:ok, teme} = Sidereon.propagate(el, ~U[2018-07-04 00:00:00Z])
iex> {x, _y, _z} = teme.position
iex> x > 3000 and x < 4000
true

rinex_clock_to_string(clock)

See Sidereon.GNSS.RINEX.Clock.to_rinex_string/1.

rtcm_derive_lli(previous_min, elapsed_ms, current_min, half_cycle?)

See Sidereon.GNSS.RTCM.derive_lli/4.

rtcm_lli_bits()

See Sidereon.GNSS.RTCM.lli_bits/0.

rtcm_message_number(body)

See Sidereon.GNSS.RTCM.message_number/1.

rtcm_minimum_lock_time_ms(kind, indicator)

See Sidereon.GNSS.RTCM.minimum_lock_time_ms/2.

rtcm_msm_epoch_dt_ms(system, previous, current)

See Sidereon.GNSS.RTCM.msm_epoch_dt_ms/3.

rtcm_msm_lli(messages)

See Sidereon.GNSS.RTCM.msm_lli/1.

rtcm_msm_signal_rinex_code(system, signal_id)

See Sidereon.GNSS.RTCM.msm_signal_rinex_code/2.

screen_tca_candidates(primary_line1, primary_line2, secondaries, window_start_jd, window_end_jd, miss_distance_threshold_km, opts \\ [])

See Sidereon.Conjunction.screen_tca_candidates/7.

screen_tca_conjunctions(primary_line1, primary_line2, secondaries, window_start_jd, window_end_jd, miss_distance_threshold_km, hard_body_radius_km, opts \\ [])

See Sidereon.Conjunction.screen_tca_conjunctions/8.

solid_earth_pole_tide(station_ecef_m, year, month, day, fhr, xp_arcsec, yp_arcsec)

@spec solid_earth_pole_tide(
  vec3(),
  integer(),
  integer(),
  integer(),
  number(),
  number(),
  number()
) ::
  {:ok, vec3()} | {:error, term()}

Compute solid-earth pole tide station displacement in metres, ECEF.

solid_earth_tide(station_ecef_m, year, month, day, fhr, sun_ecef_m, moon_ecef_m)

@spec solid_earth_tide(
  vec3(),
  integer(),
  integer(),
  integer(),
  number(),
  vec3(),
  vec3()
) ::
  {:ok, vec3()} | {:error, term()}

Compute solid-earth tide station displacement in metres, ECEF.

solve_ppp_fixed(sp3, epochs, float_solution, opts \\ [])

See Sidereon.GNSS.PrecisePositioning.solve_ppp_fixed/4.

solve_ppp_float(sp3, epochs, initial_state, opts \\ [])

See Sidereon.GNSS.PrecisePositioning.solve_ppp_float/4.

solve_rtk_fixed(config)

See Sidereon.GNSS.RTK.solve_rtk_fixed/1.

solve_rtk_float(config)

See Sidereon.GNSS.RTK.solve_rtk_float/1.

sun_angle(satellite_gcrs_position, sun_position_from_earth)

@spec sun_angle(vec3(), vec3()) :: float()

Compute the angle between satellite nadir and the Sun direction.

See Sidereon.Angles.sun_angle/2 for details.

sun_moon_ecef(epochs_unix_us)

@spec sun_moon_ecef([integer()]) ::
  {:ok, %{sun: [vec3()], moon: [vec3()]}} | {:error, term()}

Compute Sun and Moon ECEF vectors for UTC Unix microsecond epochs.

sun_moon_eci(epochs_unix_us)

@spec sun_moon_eci([integer()]) ::
  {:ok, %{sun: [vec3()], moon: [vec3()]}} | {:error, term()}

Compute Sun and Moon ECI vectors for UTC Unix microsecond epochs.

teme_to_gcrs(teme_state, datetime, opts \\ [])

@spec teme_to_gcrs(
  Sidereon.TemeState.t() | gcrs_state(),
  DateTime.t() | tuple(),
  keyword()
) ::
  gcrs_state()

Convert a TEME state vector to GCRS (Geocentric Celestial Reference System).

Set skyfield_compat: true to reproduce the committed Skyfield oracle vectors used by the validation suite. The default is sidereon's native path.

Example

gcrs = Sidereon.teme_to_gcrs(teme, datetime)
gcrs = Sidereon.teme_to_gcrs(teme, datetime, skyfield_compat: true)

timescale_offset(from, to)

Fixed inter-system time offset to - from, in seconds, for the atomic scales.

See Sidereon.GNSS.Time.timescale_offset/2 for the scale names and the epoch-required/unsupported error cases.

timescale_offset_at(from, to, utc_jd)

Leap-aware inter-system time offset to - from, in seconds, at utc_jd.

See Sidereon.GNSS.Time.timescale_offset_at/3.

write_rinex_clock(clock, path)

See Sidereon.GNSS.RINEX.Clock.write/2.