Current section

Files

Jump to
sidereon lib sidereon astro equinoctial.ex
Raw

lib/sidereon/astro/equinoctial.ex

defmodule Sidereon.Astro.Equinoctial do
@moduledoc """
Equinoctial and modified equinoctial orbital element conversions.
"""
alias Sidereon.NIF
alias Sidereon.OrbitalElements
defmodule EquinoctialElements do
@moduledoc """
Equinoctial orbital elements.
"""
@enforce_keys [:a, :h, :k, :p, :q, :lambda, :retrograde]
defstruct [:a, :h, :k, :p, :q, :lambda, :retrograde]
@type retrograde_factor :: :prograde | :retrograde
@type t :: %__MODULE__{
a: float(),
h: float(),
k: float(),
p: float(),
q: float(),
lambda: float(),
retrograde: retrograde_factor()
}
end
defmodule ModifiedEquinoctialElements do
@moduledoc """
Modified equinoctial orbital elements.
"""
@enforce_keys [:p, :f, :g, :h, :k, :l, :retrograde]
defstruct [:p, :f, :g, :h, :k, :l, :retrograde]
@type t :: %__MODULE__{
p: float(),
f: float(),
g: float(),
h: float(),
k: float(),
l: float(),
retrograde: EquinoctialElements.retrograde_factor()
}
end
@type vec3 :: {number(), number(), number()}
def coe2eq(%OrbitalElements{} = coe, factor \\ :prograde),
do: call_eq(:equinoctial_coe2eq, [classical_map(coe), factor_string(factor)])
def eq2coe(%EquinoctialElements{} = eq), do: call_classical(:equinoctial_eq2coe, [eq_map(eq)])
def coe2mee(%OrbitalElements{} = coe, factor \\ :prograde),
do: call_mee(:equinoctial_coe2mee, [classical_map(coe), factor_string(factor)])
def mee2coe(%ModifiedEquinoctialElements{} = mee), do: call_classical(:equinoctial_mee2coe, [mee_map(mee)])
def rv2eq(r, v, mu \\ OrbitalElements.mu_earth(), factor \\ :prograde) do
call_eq(:equinoctial_rv2eq, [floats3(r), floats3(v), mu / 1.0, factor_string(factor)])
end
def eq2rv(%EquinoctialElements{} = eq, mu \\ OrbitalElements.mu_earth()) do
case NIF.equinoctial_eq2rv(eq_map(eq), mu / 1.0) do
{r, v} -> {:ok, %{position_km: r, velocity_km_s: v}}
end
rescue
e in ErlangError -> {:error, e.original}
end
def rv2mee(r, v, mu \\ OrbitalElements.mu_earth(), factor \\ :prograde) do
call_mee(:equinoctial_rv2mee, [floats3(r), floats3(v), mu / 1.0, factor_string(factor)])
end
def mee2rv(%ModifiedEquinoctialElements{} = mee, mu \\ OrbitalElements.mu_earth()) do
case NIF.equinoctial_mee2rv(mee_map(mee), mu / 1.0) do
{r, v} -> {:ok, %{position_km: r, velocity_km_s: v}}
end
rescue
e in ErlangError -> {:error, e.original}
end
def eq2mee(%EquinoctialElements{} = eq), do: call_mee(:equinoctial_eq2mee, [eq_map(eq)])
def mee2eq(%ModifiedEquinoctialElements{} = mee), do: call_eq(:equinoctial_mee2eq, [mee_map(mee)])
def coe2eq!(coe, factor \\ :prograde), do: bang(coe2eq(coe, factor))
def eq2coe!(eq), do: bang(eq2coe(eq))
def coe2mee!(coe, factor \\ :prograde), do: bang(coe2mee(coe, factor))
def mee2coe!(mee), do: bang(mee2coe(mee))
def rv2eq!(r, v, mu \\ OrbitalElements.mu_earth(), factor \\ :prograde), do: bang(rv2eq(r, v, mu, factor))
def eq2rv!(eq, mu \\ OrbitalElements.mu_earth()), do: bang(eq2rv(eq, mu))
def rv2mee!(r, v, mu \\ OrbitalElements.mu_earth(), factor \\ :prograde), do: bang(rv2mee(r, v, mu, factor))
def mee2rv!(mee, mu \\ OrbitalElements.mu_earth()), do: bang(mee2rv(mee, mu))
def eq2mee!(eq), do: bang(eq2mee(eq))
def mee2eq!(mee), do: bang(mee2eq(mee))
defp call_eq(fun, args), do: decode(apply(NIF, fun, args), &to_eq/1)
defp call_mee(fun, args), do: decode(apply(NIF, fun, args), &to_mee/1)
defp call_classical(fun, args), do: decode(apply(NIF, fun, args), &to_classical/1)
defp decode({:ok, fields}, mapper), do: {:ok, mapper.(fields)}
defp decode({:error, reason}, _mapper), do: {:error, reason}
defp to_eq(fields) do
%EquinoctialElements{
a: fields.a,
h: fields.h,
k: fields.k,
p: fields.p,
q: fields.q,
lambda: fields.lambda,
retrograde: retrograde_atom(fields.retrograde)
}
end
defp to_mee(fields) do
%ModifiedEquinoctialElements{
p: fields.p,
f: fields.f,
g: fields.g,
h: fields.h,
k: fields.k,
l: fields.l,
retrograde: retrograde_atom(fields.retrograde)
}
end
defp to_classical(fields) do
%OrbitalElements{
p: fields.p,
a: fields.a,
ecc: fields.ecc,
incl: fields.incl,
raan: fields.raan,
argp: fields.argp,
nu: fields.nu,
arglat: fields.arglat,
truelon: fields.truelon,
lonper: fields.lonper,
orbit_type: orbit_type_atom(fields.orbit_type)
}
end
defp classical_map(%OrbitalElements{} = coe),
do: coe |> Map.from_struct() |> Map.update!(:orbit_type, &Atom.to_string/1)
defp eq_map(%EquinoctialElements{} = eq), do: eq |> Map.from_struct() |> Map.update!(:retrograde, &Atom.to_string/1)
defp mee_map(%ModifiedEquinoctialElements{} = mee),
do: mee |> Map.from_struct() |> Map.update!(:retrograde, &Atom.to_string/1)
defp factor_string(factor) when factor in [:prograde, :retrograde], do: Atom.to_string(factor)
defp floats3({x, y, z}), do: {x / 1.0, y / 1.0, z / 1.0}
defp retrograde_atom("prograde"), do: :prograde
defp retrograde_atom("retrograde"), do: :retrograde
defp retrograde_atom(other), do: other
defp orbit_type_atom("elliptic"), do: :elliptic
defp orbit_type_atom("circular"), do: :circular
defp orbit_type_atom("parabolic"), do: :parabolic
defp orbit_type_atom("hyperbolic"), do: :hyperbolic
defp orbit_type_atom("elliptical_inclined"), do: :elliptical_inclined
defp orbit_type_atom("elliptical_equatorial"), do: :elliptical_equatorial
defp orbit_type_atom("circular_inclined"), do: :circular_inclined
defp orbit_type_atom("circular_equatorial"), do: :circular_equatorial
defp orbit_type_atom(other), do: other
defp bang({:ok, value}), do: value
defp bang({:error, reason}), do: raise(ArgumentError, "equinoctial conversion failed: #{inspect(reason)}")
end