Orbit Determination

In this notebook, we solve a simple orbit determination problem of estimating an orbit to some sample GNSS data.

import pandas as pd

import plotly.express as px

import numpy as np

from ostk.physics import Environment
from ostk.physics.coordinate import Frame
from ostk.physics.environment.object.celestial import Earth
from ostk.physics.environment.object.celestial import Moon
from ostk.physics.environment.object.celestial import Sun

from ostk.astrodynamics.estimator import OrbitDeterminationSolver
from ostk.astrodynamics.trajectory import State
from ostk.astrodynamics.solver import LeastSquaresSolver
from ostk.astrodynamics.trajectory.state import NumericalSolver
from ostk.astrodynamics.dataframe import generate_states_from_dataframe
from ostk.astrodynamics.converters import coerce_to_datetime

Setup environment

We setup an environment with Earth as the central celestial object

  • EGM96 10x10

  • No atmospheric drag

  • Third body perturbations from the Sun and the Moon

environment: Environment = Environment(
    central_celestial_object=Earth.EGM96(10, 10),
    objects=[Moon.default(), Sun.default()]
)
orbit_determination_solver: OrbitDeterminationSolver = OrbitDeterminationSolver(
    environment=environment,
    numerical_solver=NumericalSolver.default(),
    solver=LeastSquaresSolver.default(),
)

Load sample data

Sample GNSS telemetry in the ITRF frame

gnss_data = pd.DataFrame(
  [
    {
      "Timestamp": "2022-09-10T00:00:00.537",
      "r_ITRF_x": -1016988.4,
      "v_ITRF_x": -6440.7055664062,
      "v_ITRF_z": -1946.8566894531,
      "r_ITRF_y": -1698967.28,
      "v_ITRF_y": -3691.6062011719,
      "r_ITRF_z": 6597796.2199999997
    },
    {
      "Timestamp": "2022-09-10T00:01:00.537",
      "r_ITRF_x": -1401893.8,
      "v_ITRF_x": -6384.5068359375,
      "v_ITRF_z": -2423.8815917969,
      "r_ITRF_y": -1914926.0600000001,
      "v_ITRF_y": -3504.4743652344,
      "r_ITRF_z": 6466623.4699999997
    },
    {
      "Timestamp": "2022-09-10T00:02:00.537",
      "r_ITRF_x": -1782540.96,
      "v_ITRF_x": -6298.8227539062,
      "v_ITRF_z": -2890.572265625,
      "r_ITRF_y": -2119207.3999999999,
      "v_ITRF_y": -3302.5617675781,
      "r_ITRF_z": 6307130.9400000004
    },
    {
      "Timestamp": "2022-09-10T00:04:59.537",
      "r_ITRF_x": -2875526.4500000002,
      "v_ITRF_x": -5870.8212890625,
      "v_ITRF_z": -4200.7197265625,
      "r_ITRF_y": -2651245.2000000002,
      "v_ITRF_y": -2624.5534667969,
      "r_ITRF_z": 5670406.9900000002
    },
    {
      "Timestamp": "2022-09-10T00:05:00.537",
      "r_ITRF_x": -2881395.6899999999,
      "v_ITRF_x": -5867.7138671875,
      "v_ITRF_z": -4207.5874023438,
      "r_ITRF_y": -2653867.7200000002,
      "v_ITRF_y": -2620.478515625,
      "r_ITRF_z": 5666202.8399999999
    },
    {
      "Timestamp": "2022-09-10T00:06:00.537",
      "r_ITRF_x": -3227593.21,
      "v_ITRF_x": -5667.6708984375,
      "v_ITRF_z": -4612.5078125,
      "r_ITRF_y": -2803680.5899999999,
      "v_ITRF_y": -2371.7475585938,
      "r_ITRF_z": 5401503.0199999996
    },
    {
      "Timestamp": "2022-09-10T00:07:00.537",
      "r_ITRF_x": -3560975.0099999998,
      "v_ITRF_x": -5440.666015625,
      "v_ITRF_z": -4997.3012695312,
      "r_ITRF_y": -2938306.2599999998,
      "v_ITRF_y": -2114.4851074219,
      "r_ITRF_z": 5113105.0499999998
    },
    {
      "Timestamp": "2022-09-10T00:11:18.537",
      "r_ITRF_x": -4811601.4199999999,
      "v_ITRF_x": -4181.2783203125,
      "v_ITRF_z": -6383.759765625,
      "r_ITRF_y": -3334481.5099999998,
      "v_ITRF_y": -943.7043457031,
      "r_ITRF_z": 3634961.1600000001
    },
    {
      "Timestamp": "2022-09-10T00:12:00.537",
      "r_ITRF_x": -4982122.6600000001,
      "v_ITRF_x": -3937.1262207031,
      "v_ITRF_z": -6563.2080078125,
      "r_ITRF_y": -3370012.2599999998,
      "v_ITRF_y": -748.1647949219,
      "r_ITRF_z": 3363025.0299999998
    },
    {
      "Timestamp": "2022-09-10T00:13:00.537",
      "r_ITRF_x": -5207480.3899999997,
      "v_ITRF_x": -3571.6452636719,
      "v_ITRF_z": -6795.056640625,
      "r_ITRF_y": -3406514.5600000001,
      "v_ITRF_y": -468.665222168,
      "r_ITRF_z": 2962129.3199999998
    },
    {
      "Timestamp": "2022-09-10T00:14:33.537",
      "r_ITRF_x": -5511988.8300000001,
      "v_ITRF_x": -2970.1599121094,
      "v_ITRF_z": -7095.07421875,
      "r_ITRF_y": -3430052.2799999998,
      "v_ITRF_y": -38.3974189758,
      "r_ITRF_z": 2315670.1800000002
    }
  ]
)
observations: list[State] = generate_states_from_dataframe(gnss_data, reference_frame=Frame.ITRF())

Solve

Using the first observation as an initial guess, generate an orbit determination analysis with an estimated state

initial_guess_state: State = observations[0]
analysis: OrbitDeterminationSolver.Analysis = orbit_determination_solver.estimate(
    initial_guess=initial_guess_state,
    observations=observations,
)
print(analysis)
-- Orbit Determination Solver Analysis -------------------------------------------------------------
    Estimated State - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Instant:                                 2022-09-10 00:00:00.537 [UTC]            
    Frame:                                   GCRF                                     
    CARTESIAN_POSITION                       [-1315154.2247, -1467133.1199, 6600721.3522] 
    CARTESIAN_VELOCITY                       [-6935.4440, -2458.0728, -1931.5970]     
    Analysis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-- Least Squares Solver Analysis -------------------------------------------------------------------
    RMS Error:                               1.52384                                  
    Iteration Count:                         3                                        
    Termination Criteria:                    RMS Update Threshold                     
    Estimated State - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Instant:                                 2022-09-10 00:00:00.537 [UTC]            
    Frame:                                   GCRF                                     
    CARTESIAN_POSITION                       [-1315154.2247, -1467133.1199, 6600721.3522] 
    CARTESIAN_VELOCITY                       [-6935.4440, -2458.0728, -1931.5970]     
    Steps - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    RMS Error:                               39.8482                                  
    X Hat:                                   [-0.3475, -1.6514, -3.2836, -0.0218, -0.0206, 0.0630] 
    RMS Error:                               1.52384                                  
    X Hat:                                   [0.0000, 0.0000, -0.0000, -0.0000, -0.0000, 0.0000] 
    RMS Error:                               1.52384                                  
    X Hat:                                   [0.0000, 0.0000, 0.0000, -0.0000, -0.0000, -0.0000] 
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
residuals = analysis.solver_analysis.compute_residual_states(observations)

data = []
for residual in residuals:
    data.append(
        {
            "timestamp": coerce_to_datetime(residual.get_instant()),
            "dr [m]": np.linalg.norm(residual.get_position().get_coordinates()),
        }
    )
figure = px.scatter(data, x="timestamp", y="dr [m]", title="Definitive position residuals")
figure.show("svg")
../_images/e64f56db9e6d95a60e77a02fe2701785cb81f67b9dd5d0d65a5adf980ff2a056.svg