Skip to content

Orbit Determination Examples

Creating a New Orbit State

make_od.py
import json
from keplemon.bodies import Observatory, Sensor, Satellite
from keplemon.estimation import Observation, BatchLeastSquares
from keplemon.time import Epoch
from keplemon.enums import TimeSystem, KeplerianType
from keplemon.elements import TopocentricElements, TLE

# Update your a priori state here
tle = TLE.from_lines(
    "1 41838U 16065A   25127.90338593  .00000067  00000+0  00000+0 0  9990",
    "2 41838   4.7224  77.5036 0005258 282.3676  13.3050  1.00272812 31141",
)
sat = Satellite.from_tle(tle)

# This example is built using UDL-formatted obs
with open("example-obs.json", "r") as f:
    json_obs = json.load(f)

keplemon_obs = []

# This can be updated per observation to reflect actual sensor capabilities
dummy_sensor = Sensor(name="Fake Sensor", angular_noise=0.001)

for json_ob in json_obs:

    # Keys here are only available because the input example data is UDL-formatted
    epoch = Epoch.from_iso(json_ob["obTime"], TimeSystem.UTC)
    lat = json_ob["senlat"]
    lon = json_ob["senlon"]
    alt = json_ob["senalt"]
    site = Observatory("Dummy Site", lat, lon, alt)
    observer_teme = site.get_state_at_epoch(epoch)

    # KepLemon expects TEME.  The `from_j2000` constructor will be needed for most observations
    teme_topo = TopocentricElements.from_j2000(epoch, json_ob["ra"], json_ob["declination"])

    ob = Observation(dummy_sensor, epoch, teme_topo, observer_teme.position)
    keplemon_obs.append(ob)


# Create the BLS object
bls = BatchLeastSquares(keplemon_obs, sat)

# You can override the output type if you'd like to use a more accurate model than basic SGP4
bls.output_type = KeplerianType.MeanBrouwerXP

# This will iterate until the weighted RMS tolerance is achieved OR max iterations is achieved
try:
    bls.solve()
except RuntimeError as e:
    print(e)

Plotting OD Results

plot_od.py
# Add code to create a new orbit state as seen in the make_od.py example
...

# Be sure to add additional imports for plotting
import plotly.graph_objects as go  # type: ignore
import plotly.offline as pyo  # type: ignore

times, a_priori_errors, fit_errors = [], [], []
for ob in keplemon_obs:
    times.append(epoch.to_iso())
    a_priori_errors.append(ob.get_residual(sat).range)
    fit_errors.append(ob.get_residual(bls.current_estimate).range)


# Add the data to a plotly object
fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=times,
        y=a_priori_errors,
        mode="markers",
        name="A Priori Errors",
        marker=dict(color="blue"),
    )
)
fig.add_trace(
    go.Scatter(
        x=times,
        y=fit_errors,
        mode="markers",
        name="Fit Errors",
        marker=dict(color="red"),
    )
)
fig.update_layout(
    title="A Priori and Fit Errors",
    xaxis_title="Epoch (UTC)",
    yaxis_title="Error (km)",
    legend=dict(x=0, y=1),
    xaxis_tickformat="%Y-%m-%d %H:%M:%S",
)

fig.show()