Section # 1 - First, we store the data into an array after initial cleaning (here, it is done manually and weird looking data points are removed by visual inspection). Further, the data is plotted to visualize the given information.

In [3]:
import json
import numpy as np
import matplotlib.pyplot as plt

# load ephemeris data from the gps.json file
with open('gps.json') as f: data_f = json.load(f)

# Matrix [7,33]
data = np.array([[sub['gnss.sample.t_j2000'] for sub in data_f], [sub['gnss.sample.rx_ecef'] for sub in data_f], [sub['gnss.sample.ry_ecef'] for sub in data_f],[sub['gnss.sample.rz_ecef'] for sub in data_f], \
                [sub['gnss.sample.vx_ecef'] for sub in data_f],[sub['gnss.sample.vy_ecef'] for sub in data_f],[sub['gnss.sample.vz_ecef'] for sub in data_f]])

time_UTC =  np.array([sub['t'] for sub in data_f])  
# Rows: [t,x,y,z,vx,vy,vz]' 
# Columns: [1,2,...., 33] datapoints

data_modified = np.delete(data,16,1) # data point deleted because all measurements are 0
time_UTC = np.delete(time_UTC,16)

time = data_modified[0,:]
x_gps, y_gps, z_gps = data_modified[1,:], data_modified[2,:], data_modified[3,:]
vx_gps, vy_gps, vz_gps = data_modified[4,:], data_modified[5,:], data_modified[6,:]

# Plot raw GPS position measurements
fig, axs = plt.subplots(3)
plt.suptitle("GPS Position measurements")
axs[0].plot(time,x_gps,'r*')
axs[0].set(ylabel='x coord.')
axs[1].plot(time,y_gps,'b*')
axs[1].set(ylabel='y coord.')
axs[2].plot(time,z_gps,'g*')
axs[2].set(xlabel='time',ylabel='z coord.')
for ax in axs.flat:ax.label_outer()

# Plot raw GPS velocity measurements
fig, axs = plt.subplots(3)
plt.suptitle("GPS Velocity measurements")
axs[0].plot(time,vx_gps,'r*')
axs[0].set(ylabel='x velocity')
axs[1].plot(time,vy_gps,'b*')
axs[1].set(ylabel='y velocity')
axs[2].plot(time,vz_gps,'g*')
axs[2].set(xlabel='time',ylabel='velocity')
for ax in axs.flat:ax.label_outer()
In [4]:
# loading orekit library for orbital analysis
import orekit
orekit.initVM()

from orekit.pyhelpers import setup_orekit_curdir
setup_orekit_curdir()

Section # 2 - Save Measurements in different frames

In [5]:
from org.hipparchus.geometry.euclidean.threed import Vector3D
from org.orekit.frames import FramesFactory
from org.orekit.utils import IERSConventions, Constants, PVCoordinates, PVCoordinatesProvider, AbsolutePVCoordinates
from orekit.pyhelpers import datetime_to_absolutedate
from dateutil import parser


ITRF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)
GCRF = FramesFactory.getGCRF()
EME = FramesFactory.getEME2000()

pos_gcrf = []
vel_gcrf = []

pos_itrf = []
vel_itrf = []

pos_eme = []
vel_eme = []

for i in range(32):
    pos_ind = Vector3D(data_modified[1:4,i].tolist())
    pos_itrf.append(pos_ind)
    
    vel_ind = Vector3D(data_modified[4:7,i].tolist())
    vel_itrf.append(vel_ind)
    
    pv_itrf = PVCoordinates(pos_ind, vel_ind)
    time_index = datetime_to_absolutedate(parser.parse(time_UTC[i]))
    
    pv_gcrf = ITRF.getTransformTo(GCRF, time_index).transformPVCoordinates(pv_itrf)
    pos_gcrf.append(pv_gcrf.getPosition())
    vel_gcrf.append(pv_gcrf.getVelocity())
    
    pv_eme = ITRF.getTransformTo(EME, time_index).transformPVCoordinates(pv_itrf)
    pos_eme.append(pv_eme.getPosition())
    vel_eme.append(pv_eme.getVelocity())

Transfer saved measurements to pandas dataframe type

In [6]:
import pandas as pd
position_rawGPS = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])
position_GCRF = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])
position_EME = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])

for i in range(32):
    time = data_modified[0,i]
    pos = data_modified[1:4,i]
    vel = data_modified[4:7,i]
    position_rawGPS.loc[parser.parse(time_UTC[i])] = np.concatenate((pos,vel),axis=0)
    
    pos_gcrf_ind = np.array(pos_gcrf[i].toArray())
    vel_gcrf_ind = np.array(vel_gcrf[i].toArray())
    position_GCRF.loc[parser.parse(time_UTC[i])] = np.concatenate((pos_gcrf_ind,vel_gcrf_ind),axis=0)
    
    pos_eme_ind = np.array(pos_eme[i].toArray())
    vel_eme_ind = np.array(vel_eme[i].toArray())
    position_EME.loc[parser.parse(time_UTC[i])] = np.concatenate((pos_eme_ind,vel_eme_ind),axis=0)
    

print('Raw GPS Measurements')
display(position_rawGPS)
print('GPS Measurements in GCRF Frame')
display(position_GCRF)
print('GPS Measurements in EME2000 Frame')
display(position_EME)
Raw GPS Measurements
x y z vx vy vz
2020-09-01 00:27:31.673 -4.263229e+06 2.255334e+06 4.878473e+06 -3906.714643 4036.866952 -5258.367963
2020-09-01 00:48:52.204 -3.905573e+06 4.039656e+06 -3.963623e+06 4159.788939 -1996.710761 -6141.699999
2020-09-01 01:46:40.274 2.549982e+05 -1.859307e+06 6.590330e+06 -4554.583929 5917.890573 1845.319139
2020-09-01 02:21:12.153 -2.318428e+06 5.643614e+06 -3.169715e+06 2744.104440 -2619.500528 -6684.509295
2020-09-01 03:18:07.254 -1.682650e+05 -3.037769e+06 6.140334e+06 -2058.926405 6669.837457 3238.145759
2020-09-01 03:47:52.086 -5.674824e+05 6.844673e+06 2.039498e+05 1451.861411 358.273053 -7549.759620
2020-09-01 04:43:58.286 -1.419562e+06 -5.661965e+06 3.597773e+06 -561.477451 4230.935934 6414.518712
2020-09-01 05:20:31.552 2.052237e+06 6.481550e+06 9.863924e+05 1743.994101 600.383498 -7472.863998
2020-09-01 07:03:30.967 4.640124e+06 4.218785e+06 -2.818709e+06 -1254.405292 -3202.904419 -6874.617009
2020-09-01 07:59:16.286 -3.573520e+06 -1.339617e+06 5.692204e+06 5065.212009 4071.128993 4128.825531
2020-09-01 08:40:11.496 5.537592e+06 1.657044e+06 -3.724783e+06 -3444.482422 -2681.099744 -6322.481070
2020-09-01 10:04:50.842 6.854204e+06 9.364834e+04 4.699154e+05 549.837377 -1470.575408 -7534.638970
2020-09-01 10:17:00.439 5.049619e+06 -8.385247e+05 -4.592288e+06 -5209.282558 -840.719742 -5577.913917
2020-09-01 11:19:48.314 1.606741e+06 3.706958e+05 6.652777e+06 7044.146233 -2670.461501 -1543.885848
2020-09-01 11:46:02.220 5.639491e+06 -2.996024e+06 -2.547053e+06 -3170.895574 -31.317353 -7003.997301
2020-09-01 11:55:01.713 3.071301e+06 -2.407209e+06 -5.662649e+06 -6034.845279 2186.018085 -4202.070379
2020-09-01 13:24:22.273 3.158081e+06 -4.529358e+06 -4.098378e+06 -3884.093374 2743.978885 -6031.762224
2020-09-01 14:27:02.274 1.089234e+06 -2.755912e+05 6.760630e+06 3062.270547 -7011.535541 -773.122428
2020-09-01 14:53:41.680 1.776908e+06 -6.310699e+06 -2.068155e+06 -2056.617088 1769.691658 -7195.779816
2020-09-01 16:01:24.231 8.876310e+05 -6.765030e+05 6.761996e+06 2.921937 -7651.868335 -759.906342
2020-09-01 16:25:01.883 -6.605757e+05 -6.803513e+06 -7.145346e+05 -1396.909213 913.276372 -7511.179516
2020-09-01 17:53:31.235 -2.756144e+06 -5.986412e+06 1.932886e+06 -2254.636126 -1317.126030 -7241.497611
2020-09-01 19:19:11.750 -2.862823e+06 -3.448604e+06 5.192878e+06 -5022.672625 -3197.730872 -4872.607700
2020-09-01 19:37:50.403 -5.305779e+06 -3.600129e+06 -2.480587e+06 1395.249853 2775.098859 -7033.330380
2020-09-01 20:51:21.397 -3.177532e+06 -1.861803e+06 5.784816e+06 -6450.150882 -1358.641709 -3964.694900
2020-09-01 21:01:21.947 -6.112212e+06 -2.083750e+06 2.335523e+06 -2930.654144 595.058881 -7093.340182
2020-09-01 21:12:11.204 -6.309309e+06 -1.180150e+06 -2.462576e+06 2369.328552 1981.553434 -7041.246605
2020-09-01 21:18:50.992 -4.763295e+06 -3.390212e+05 -4.948345e+06 5239.073028 2129.130133 -5190.870131
2020-09-01 22:06:55.254 4.429692e+06 -6.894608e+05 5.184785e+06 -5877.351885 -928.515917 4885.246277
2020-09-01 22:27:51.017 -4.431890e+06 -2.545523e+05 5.229168e+06 -5801.567213 1503.404083 -4824.131098
2020-09-01 22:47:30.944 -6.074848e+06 1.489504e+06 -2.854388e+06 3395.660482 740.396700 -6856.806594
2020-09-01 23:41:02.319 3.876756e+06 -2.428986e+06 5.104095e+06 -5684.505436 1438.910094 4990.722059
GPS Measurements in GCRF Frame
x y z vx vy vz
2020-09-01 00:27:31.673 -3.650988e+06 3.140303e+06 4.885686e+06 -3159.257467 4532.316600 -5252.151540
2020-09-01 00:48:52.204 -3.360986e+06 4.508715e+06 -3.957006e+06 3527.277716 -2760.032009 -6148.672498
2020-09-01 01:46:40.274 4.957173e+05 -1.813552e+06 6.589369e+06 -5114.755021 5345.219204 1855.409836
2020-09-01 02:21:12.153 -3.769667e+06 4.802303e+06 -3.162289e+06 2988.761896 -2050.656908 -6690.419853
2020-09-01 03:18:07.254 1.386237e+06 -2.714421e+06 6.137615e+06 -4915.315837 4845.205641 3247.847008
2020-09-01 03:47:52.086 -4.614551e+06 5.086621e+06 2.130513e+05 548.367187 831.155547 -7550.858973
2020-09-01 04:43:58.286 3.557851e+06 -4.633044e+06 3.590763e+06 -3311.756890 2450.692682 6421.066026
2020-09-01 05:20:31.552 -4.646512e+06 4.961241e+06 9.955622e+05 -46.793020 1475.745504 -7472.787888
2020-09-01 07:03:30.967 -3.935174e+06 4.887433e+06 -2.810946e+06 2750.953341 -1732.896852 -6880.061495
2020-09-01 07:59:16.286 1.979653e+06 -3.269549e+06 5.688306e+06 -4679.441349 4382.021721 4138.074873
2020-09-01 08:40:11.496 -3.519485e+06 4.590822e+06 -3.717838e+06 3380.596943 -2526.746525 -6329.171006
2020-09-01 10:04:50.842 -4.653261e+06 5.032626e+06 4.791134e+05 343.546430 1052.974721 -7535.331611
2020-09-01 10:17:00.439 -2.988823e+06 4.162074e+06 -4.586389e+06 3964.989439 -3304.209492 -5585.761332
2020-09-01 11:19:48.314 -1.570017e+06 4.610809e+05 6.655894e+06 -4879.905272 5654.681717 -1534.239864
2020-09-01 11:46:02.220 -4.055515e+06 4.936960e+06 -2.539039e+06 2564.467791 -1487.746476 -7009.081320
2020-09-01 11:55:01.713 -2.050825e+06 3.326778e+06 -5.658604e+06 4643.509885 -4301.161907 -4211.260790
2020-09-01 13:24:22.273 -3.322142e+06 4.416524e+06 -4.091815e+06 3643.383082 -2848.588478 -6038.980369
2020-09-01 14:27:02.274 -1.108073e+06 -6.875408e+04 6.762835e+06 -5046.265845 5666.183268 -763.142805
2020-09-01 14:53:41.680 -4.228349e+06 5.013771e+06 -2.059796e+06 2224.003980 -1070.549538 -7200.193862
2020-09-01 16:01:24.231 -1.099869e+06 -7.882516e+04 6.764186e+06 -5054.521908 5659.889384 -749.912512
2020-09-01 16:25:01.883 -4.552207e+06 5.100426e+06 -7.055349e+05 1231.809712 75.012823 -7513.632615
2020-09-01 17:53:31.235 -4.635728e+06 4.680594e+06 1.942053e+06 -798.381464 2235.022977 -7239.937676
2020-09-01 19:19:11.750 -3.485145e+06 2.805476e+06 5.199775e+06 -3494.482157 4714.255815 -4865.717411
2020-09-01 19:37:50.403 -4.107355e+06 4.927678e+06 -2.472480e+06 2525.151273 -1415.935048 -7038.336938
2020-09-01 20:51:21.397 -2.983677e+06 2.142972e+06 5.790722e+06 -4033.440489 5118.110230 -3956.742868
2020-09-01 21:01:21.947 -4.592118e+06 4.535541e+06 2.344595e+06 -1122.851792 2552.231764 -7091.143210
2020-09-01 21:12:11.204 -4.119263e+06 4.926638e+06 -2.454451e+06 2513.827819 -1397.963190 -7046.228415
2020-09-01 21:18:50.992 -2.749699e+06 3.911090e+06 -4.942930e+06 4224.426418 -3598.575019 -5199.223146
2020-09-01 22:06:55.254 2.526575e+06 -3.710186e+06 5.179813e+06 -4406.726313 3849.291050 4893.955832
2020-09-01 22:27:51.017 -3.465084e+06 2.761872e+06 5.236019e+06 -3537.213491 4731.952063 -4817.165405
2020-09-01 22:47:30.944 -3.975106e+06 4.833798e+06 -2.846553e+06 2795.701130 -1732.410675 -6862.341405
2020-09-01 23:41:02.319 2.602440e+06 -3.769449e+06 5.098976e+06 -4359.930963 3768.307362 4999.335498
GPS Measurements in EME2000 Frame
x y z vx vy vz
2020-09-01 00:27:31.673 -3.650988e+06 3.140303e+06 4.885686e+06 -3159.258211 4532.316203 -5252.151435
2020-09-01 00:48:52.204 -3.360987e+06 4.508714e+06 -3.957006e+06 3527.277416 -2760.031963 -6148.672691
2020-09-01 01:46:40.274 4.957180e+05 -1.813552e+06 6.589369e+06 -5114.755250 5345.218903 1855.410071
2020-09-01 02:21:12.153 -3.769668e+06 4.802303e+06 -3.162289e+06 2988.761502 -2050.656918 -6690.420026
2020-09-01 03:18:07.254 1.386238e+06 -2.714420e+06 6.137615e+06 -4915.315918 4845.205401 3247.847244
2020-09-01 03:47:52.086 -4.614551e+06 5.086621e+06 2.130515e+05 548.366520 831.155336 -7550.859045
2020-09-01 04:43:58.286 3.557852e+06 -4.633043e+06 3.590763e+06 -3311.756547 2450.692659 6421.066212
2020-09-01 05:20:31.552 -4.646513e+06 4.961241e+06 9.955624e+05 -46.793727 1475.745254 -7472.787933
2020-09-01 07:03:30.967 -3.935174e+06 4.887433e+06 -2.810946e+06 2750.952909 -1732.896884 -6880.061660
2020-09-01 07:59:16.286 1.979654e+06 -3.269549e+06 5.688306e+06 -4679.441326 4382.021527 4138.075105
2020-09-01 08:40:11.496 -3.519485e+06 4.590821e+06 -3.717838e+06 3380.596612 -2526.746495 -6329.171195
2020-09-01 10:04:50.842 -4.653261e+06 5.032625e+06 4.791136e+05 343.545749 1052.974496 -7535.331673
2020-09-01 10:17:00.439 -2.988824e+06 4.162073e+06 -4.586389e+06 3964.989223 -3304.209396 -5585.761542
2020-09-01 11:19:48.314 -1.570017e+06 4.610810e+05 6.655894e+06 -4879.905796 5654.681321 -1534.239657
2020-09-01 11:46:02.220 -4.055516e+06 4.936960e+06 -2.539039e+06 2564.467331 -1487.746526 -7009.081478
2020-09-01 11:55:01.713 -2.050826e+06 3.326777e+06 -5.658604e+06 4643.509850 -4301.161717 -4211.261022
2020-09-01 13:24:22.273 -3.322142e+06 4.416524e+06 -4.091815e+06 3643.382798 -2848.588420 -6038.980568
2020-09-01 14:27:02.274 -1.108073e+06 -6.875394e+04 6.762835e+06 -5046.266307 5666.182885 -763.142586
2020-09-01 14:53:41.680 -4.228350e+06 5.013770e+06 -2.059796e+06 2224.003476 -1070.549618 -7200.194006
2020-09-01 16:01:24.231 -1.099868e+06 -7.882501e+04 6.764186e+06 -5054.522370 5659.889002 -749.912292
2020-09-01 16:25:01.883 -4.552208e+06 5.100425e+06 -7.055347e+05 1231.809102 75.012662 -7513.632717
2020-09-01 17:53:31.235 -4.635728e+06 4.680594e+06 1.942053e+06 -798.382205 2235.022681 -7239.937685
2020-09-01 19:19:11.750 -3.485145e+06 2.805476e+06 5.199776e+06 -3494.482883 4714.255406 -4865.717285
2020-09-01 19:37:50.403 -4.107355e+06 4.927678e+06 -2.472480e+06 2525.150806 -1415.935102 -7038.337094
2020-09-01 20:51:21.397 -2.983677e+06 2.142972e+06 5.790722e+06 -4033.441170 5118.109814 -3956.742712
2020-09-01 21:01:21.947 -4.592119e+06 4.535541e+06 2.344595e+06 -1122.852544 2552.231450 -7091.143204
2020-09-01 21:12:11.204 -4.119263e+06 4.926638e+06 -2.454451e+06 2513.827350 -1397.963245 -7046.228571
2020-09-01 21:18:50.992 -2.749700e+06 3.911089e+06 -4.942930e+06 4224.426254 -3598.574892 -5199.223368
2020-09-01 22:06:55.254 2.526575e+06 -3.710185e+06 5.179813e+06 -4406.726191 3849.290899 4893.956060
2020-09-01 22:27:51.017 -3.465084e+06 2.761872e+06 5.236019e+06 -3537.214214 4731.951653 -4817.165276
2020-09-01 22:47:30.944 -3.975107e+06 4.833797e+06 -2.846553e+06 2795.700700 -1732.410704 -6862.341573
2020-09-01 23:41:02.319 2.602441e+06 -3.769449e+06 5.098976e+06 -4359.930826 3768.307218 4999.335724

Section # 3 - Make an initial guess based on three evenly spaced measurements by the Gibbs IOD method

In [7]:
from org.orekit.estimation.iod import IodGibbs
from org.orekit.utils import Constants as orekit_constants
from org.orekit.time import TimeScalesFactory
utc = TimeScalesFactory.getUTC()

# select evenly spaced three datapoints from the measurements to get an initial orbit starting point
measurement_1 = pos_gcrf[0]
date_1 = datetime_to_absolutedate(parser.parse(time_UTC[0]))
measurement_2 = pos_gcrf[10]
date_2 = datetime_to_absolutedate(parser.parse(time_UTC[10]))
measurement_3 = pos_gcrf[20]
date_3 = datetime_to_absolutedate(parser.parse(time_UTC[20]))


iod_gibbs = IodGibbs(orekit_constants.EIGEN5C_EARTH_MU)
orbit_first_guess = iod_gibbs.estimate(GCRF, measurement_1, date_1, measurement_2, date_2, measurement_3, date_3)

from org.orekit.propagation.analytical import KeplerianPropagator
kepler_propagator_iod = KeplerianPropagator(orbit_first_guess)

display(orbit_first_guess)
<KeplerianOrbit: Keplerian parameters: {a: 6856320.291893182; e: 0.0029960204441767417; i: 98.18266113710449; pa: -314.6166669549662; raan: -47.22230913097605; v: 167.72529368109466;}>

Section # 4 - Assumptions about noise in the measurements and hyperparameters for the estimations

In [8]:
sigma_position = 100e3  # Noise (in terms of standard deviation of gaussian distribution) of input position data in meters
sigma_velocity = 100.0  # Noise of input velocity data in meters per second

# Estimator parameters
estimator_position_scale = 1.0 # m
estimator_convergence_thres = 1e-2
estimator_max_iterations = 35
estimator_max_evaluations = 35

# Orbit propagator parameters
prop_min_step = 0.001 # s
prop_max_step = 300.0 # s
prop_position_error = 10.0 # m

Section # 5 - Initialize the estimator with the above hyperparemeters

In [9]:
from org.orekit.propagation.conversion import DormandPrince853IntegratorBuilder
integratorBuilder = DormandPrince853IntegratorBuilder(prop_min_step, prop_max_step, prop_position_error)

from org.orekit.propagation.conversion import NumericalPropagatorBuilder
from org.orekit.orbits import PositionAngle
propagatorBuilder = NumericalPropagatorBuilder(orbit_first_guess,
                                               integratorBuilder, PositionAngle.TRUE, estimator_position_scale)
from org.hipparchus.linear import QRDecomposer
matrix_decomposer = QRDecomposer(1e-11)
from org.hipparchus.optim.nonlinear.vector.leastsquares import GaussNewtonOptimizer
optimizer = GaussNewtonOptimizer(matrix_decomposer, False)

from org.orekit.estimation.leastsquares import BatchLSEstimator
estimator = BatchLSEstimator(optimizer, propagatorBuilder)
estimator.setParametersConvergenceThreshold(estimator_convergence_thres)
estimator.setMaxIterations(estimator_max_iterations)
estimator.setMaxEvaluations(estimator_max_evaluations)

Section # 6 - Feeding only position measurements into estimator since we don't trust velocity

In [10]:
from orekit.pyhelpers import datetime_to_absolutedate
from org.orekit.estimation.measurements import Position, ObservableSatellite

observableSatellite = ObservableSatellite(0) # Propagator index = 0

for i in range(len(pos_gcrf)):
    orekit_position = Position(
        datetime_to_absolutedate(parser.parse(time_UTC[i])),
        pos_gcrf[i],
        sigma_position, 
        1.0,  # Base weight
        observableSatellite
    )
    estimator.addMeasurement(orekit_position) 

Obtain the estimated propagated satellite state from the estimator

In [11]:
estimatedPropagatorArray = estimator.estimate()

Section # 7 - Propagate the estimator between specified bounds

In [12]:
dt = 60.0 # propagate at 1 minute intervals
date_start = datetime_to_absolutedate(parser.parse('2020-09-01T00:00:00.000000'))
date_end = datetime_to_absolutedate(parser.parse('2020-09-03T00:00:00.000000'))

# First propagating in ephemeris mode
estimatedPropagator = estimatedPropagatorArray[0]
estimatedInitialState = estimatedPropagator.getInitialState()
display(estimatedInitialState.getOrbit())
estimatedPropagator.resetInitialState(estimatedInitialState)
estimatedPropagator.setEphemerisMode()

estimatedPropagator.propagate(date_start, date_end)
bounded_propagator = estimatedPropagator.getGeneratedEphemeris()
<Orbit: Keplerian parameters: {a: 6867347.455845546; e: 0.0017628637836380144; i: 97.32467891689838; pa: -288.73537507338096; raan: -47.63360471749508; v: 141.79222484830845;}>

Section # 8 - Save results for data analysis and visualization

In [14]:
from orekit.pyhelpers import absolutedate_to_datetime
import pandas as pd

pv_bls_df = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])
pv_iod_df = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])

pv_bls_list = []
pv_iod_list = []
date_list = []

date_current = date_start
while date_current.compareTo(date_end) <= 0:
    datetime_current = absolutedate_to_datetime(date_current)    
    spacecraftState = bounded_propagator.propagate(date_current)
    
    pv_bls = spacecraftState.getPVCoordinates(GCRF)
    
    pos_bls = np.array(pv_bls.getPosition().toArray())
    vel_bls = np.array(pv_bls.getVelocity().toArray())
    pv_bls_df.loc[datetime_current] = np.concatenate((pos_bls, vel_bls))
    pv_bls_list.append(np.concatenate((pos_bls, vel_bls),axis=None))

    pv_iod_ind = kepler_propagator_iod.getPVCoordinates(date_current, GCRF)
    
    pos_iod = np.array(pv_iod_ind.getPosition().toArray())
    vel_iod = np.array(pv_iod_ind.getVelocity().toArray())
    pv_iod_df.loc[datetime_current] = np.concatenate((pos_iod,vel_iod))
    pv_iod_list.append(np.concatenate((pos_iod, vel_iod),axis=None))
    
    date_list.append(date_current)
    date_current = date_current.shiftedBy(dt) 

Section # 9 - Tranform estimated trajectory to EME2000 and ITRF frame

In [15]:
pos_bls_array = np.zeros([len(date_list),3]) # shape - [len(date_list) X (x,y,z)]
vel_bls_array = np.zeros_like(pos_bls_array)

pos_iod_array = np.zeros([len(date_list),3])
vel_iod_array = np.zeros_like(pos_iod_array)

for j in range(len(date_list)): 
    pos_bls_array[j,:] = pv_bls_list[j][0:3]
    vel_bls_array[j,:] = pv_bls_list[j][3:6]
    pos_iod_array[j,:] = pv_iod_list[j][0:3]
    vel_iod_array[j,:] = pv_iod_list[j][3:6]
    
In [16]:
ITRF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)
GCRF = FramesFactory.getGCRF()
EME = FramesFactory.getEME2000() 

est_pos_itrf = []
est_vel_itrf = []

est_pos_eme = []
est_vel_eme = []

for i in range(len(date_list)):
    pos_ind = Vector3D(pos_bls_array[i,:].tolist())    
    vel_ind = Vector3D(vel_bls_array[i,:].tolist())
    
    pv_gcrf = PVCoordinates(pos_ind, vel_ind)    
    
    pv_itrf = GCRF.getTransformTo(ITRF, date_list[i]).transformPVCoordinates(pv_gcrf)
    pv_eme = GCRF.getTransformTo(EME, date_list[i]).transformPVCoordinates(pv_gcrf)
    
    est_pos_itrf.append(pv_itrf.getPosition())
    est_vel_itrf.append(pv_itrf.getVelocity())  
    
    est_pos_eme.append(pv_eme.getPosition())
    est_vel_eme.append(pv_eme.getVelocity())
In [17]:
estimate_pv_EME2000 = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])
estimate_pv_ITRF = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])

for i in range(len(date_list)):
    pos_itrf_ind = np.array(est_pos_itrf[i].toArray())
    vel_itrf_ind = np.array(est_vel_itrf[i].toArray())
    estimate_pv_ITRF.loc[absolutedate_to_datetime(date_list[i])] = np.concatenate((pos_itrf_ind,vel_itrf_ind),axis=0)
    
    pos_eme_ind = np.array(est_pos_eme[i].toArray())
    vel_eme_ind = np.array(est_pos_eme[i].toArray())
    estimate_pv_EME2000.loc[absolutedate_to_datetime(date_list[i])] = np.concatenate((pos_eme_ind,vel_eme_ind),axis=0)

print('Estimate state in ITRF')
display(estimate_pv_ITRF)
print('Estimate state in EME2000')
display(estimate_pv_EME2000)
Estimate state in ITRF
x y z vx vy vz
2020-09-01 00:00:00 5.098615e+06 -3.193325e+06 3.293081e+06 -3905.350804 605.187414 6614.581660
2020-09-01 00:01:00 4.853362e+06 -3.148914e+06 3.682340e+06 -4266.355210 875.160841 6355.894965
2020-09-01 00:02:00 4.587076e+06 -3.088321e+06 4.055222e+06 -4606.186610 1144.376341 6068.880995
2020-09-01 00:03:00 4.301074e+06 -3.011632e+06 4.410064e+06 -4923.337812 1411.443514 5754.810137
2020-09-01 00:04:00 3.996757e+06 -2.919019e+06 4.745285e+06 -5216.414256 1674.968144 5415.075775
... ... ... ... ... ... ...
2020-09-02 23:56:00 -5.597339e+06 3.405156e+06 -2.081776e+06 2725.524370 106.396503 -7189.853155
2020-09-02 23:57:00 -5.421622e+06 3.403300e+06 -2.508255e+06 3129.163129 -168.809698 -7020.869701
2020-09-02 23:58:00 -5.222165e+06 3.384845e+06 -2.923662e+06 3516.522757 -446.667742 -6820.945437
2020-09-02 23:59:00 -4.999999e+06 3.349674e+06 -3.326167e+06 3885.897629 -725.811347 -6590.975476
2020-09-03 00:00:00 -4.756250e+06 3.297750e+06 -3.713998e+06 4235.674103 -1004.855159 -6331.985094

2881 rows × 6 columns

Estimate state in EME2000
x y z vx vy vz
2020-09-01 00:00:00 3.734712e+06 -4.721604e+06 3.285723e+06 3.734712e+06 -4.721604e+06 3.285723e+06
2020-09-01 00:01:00 3.539517e+06 -4.581911e+06 3.675368e+06 3.539517e+06 -4.581911e+06 3.675368e+06
2020-09-01 00:02:00 3.328580e+06 -4.421840e+06 4.048667e+06 3.328580e+06 -4.421840e+06 4.048667e+06
2020-09-01 00:03:00 3.102836e+06 -4.242100e+06 4.403956e+06 3.102836e+06 -4.242100e+06 4.403956e+06
2020-09-01 00:04:00 2.863288e+06 -4.043485e+06 4.739650e+06 2.863288e+06 -4.043485e+06 4.739650e+06
... ... ... ... ... ... ...
2020-09-02 23:56:00 -4.216216e+06 5.018301e+06 -2.073467e+06 -4.216216e+06 5.018301e+06 -2.073467e+06
2020-09-02 23:57:00 -4.072853e+06 4.942519e+06 -2.500230e+06 -4.072853e+06 4.942519e+06 -2.500230e+06
2020-09-02 23:58:00 -3.911513e+06 4.844921e+06 -2.915956e+06 -3.911513e+06 4.844921e+06 -2.915956e+06
2020-09-02 23:59:00 -3.732912e+06 4.725942e+06 -3.318815e+06 -3.732912e+06 4.725942e+06 -3.318815e+06
2020-09-03 00:00:00 -3.537842e+06 4.586114e+06 -3.707032e+06 -3.537842e+06 4.586114e+06 -3.707032e+06

2881 rows × 6 columns

Section # 10 - PLOTTING ESTIMATE AND MEASUREMENTS IN EME2000 FRAME

In [18]:
import plotly.graph_objects as go
# position_EME
fig = go.Figure(data=[
        go.Scatter(x=position_EME.index, y=position_EME['x'], name='X coord. GPS - measurements', mode='markers'),
        go.Scatter(x=estimate_pv_EME2000.index, y=estimate_pv_EME2000['x'], mode='lines', name='Batch least squares X-approximation (EME2000 frame)')])
fig.show()

fig = go.Figure(data=[
        go.Scatter(x=position_EME.index, y=position_EME['y'], name='Y coord. GPS - measurements', mode='markers'),
        go.Scatter(x=estimate_pv_EME2000.index, y=estimate_pv_EME2000['y'], mode='lines', name='Batch least squares Y-approximation (EME2000 frame)')])
fig.show()

fig = go.Figure(data=[
        go.Scatter(x=position_EME.index, y=position_EME['z'], name='Z coord. GPS - measurements', mode='markers'),
        go.Scatter(x=estimate_pv_EME2000.index, y=estimate_pv_EME2000['z'], mode='lines', name='Batch least squares Z-approximation (EME2000 frame)')])
fig.show()

PLOTTING ESTIMATE AND MEASUREMENTS IN ITRF FRAME (original measurements in this frame)

In [19]:
import plotly.graph_objects as go
fig = go.Figure(data=[
    go.Scatter(x=position_rawGPS.index, y=position_rawGPS['x'], name='X coord. GPS - measurements', mode='markers'),
    go.Scatter(x=estimate_pv_ITRF.index, y=estimate_pv_ITRF['x'], mode='lines', name='Batch least squares X-approximation (ITRF frame)')])
fig.show()

fig = go.Figure(data=[
    go.Scatter(x=position_rawGPS.index, y=position_rawGPS['y'], name='Y coord. GPS - measurements', mode='markers'),
    go.Scatter(x=estimate_pv_ITRF.index, y=estimate_pv_ITRF['y'], mode='lines', name='Batch least squares Y-approximation (ITRF frame)')])
fig.show()

fig = go.Figure(data=[
    go.Scatter(x=position_rawGPS.index, y=position_rawGPS['z'], name='Z coord. GPS - measurements', mode='markers'),
    go.Scatter(x=estimate_pv_ITRF.index, y=estimate_pv_ITRF['z'], mode='lines', name='Batch least squares Z-approximation (ITRF frame)')])
fig.show()

3D Orbit Visulaization of the Determined Orbit

In [20]:
# extracting points used for finding initial orbit for plotting
import math
i = math.ceil(len(position_GCRF.index) / 3)-1
points_for_iod = position_GCRF.iloc[::i, :]
points_for_iod = points_for_iod[:-1]
display(points_for_iod)
x y z vx vy vz
2020-09-01 00:27:31.673 -3.650988e+06 3.140303e+06 4.885686e+06 -3159.257467 4532.316600 -5252.151540
2020-09-01 08:40:11.496 -3.519485e+06 4.590822e+06 -3.717838e+06 3380.596943 -2526.746525 -6329.171006
2020-09-01 16:25:01.883 -4.552207e+06 5.100426e+06 -7.055349e+05 1231.809712 75.012823 -7513.632615
In [26]:
measurement_1 = pos_gcrf[0]
date_1 = datetime_to_absolutedate(parser.parse(time_UTC[0]))
measurement_2 = pos_gcrf[10]
date_2 = datetime_to_absolutedate(parser.parse(time_UTC[10]))
measurement_3 = pos_gcrf[20]
date_3 = datetime_to_absolutedate(parser.parse(time_UTC[20]))

fig_data = data=[go.Scatter3d(x=pv_iod_df['x'], y=pv_iod_df['y'], z=pv_iod_df['z'], mode='lines', name='IOD solution'),
                 go.Scatter3d(x=pv_bls_df['x'], y=pv_bls_df['y'], z=pv_bls_df['z'], mode='lines', name='Batch least squares solution'),
                 go.Scatter3d(x=position_GCRF['x'], y=position_GCRF['y'], z=position_GCRF['z'], mode='markers', name='Measurements in GCRF Frame'),
                 go.Scatter3d(x=points_for_iod['x'], y=points_for_iod['y'], z=points_for_iod['z'], mode='markers', name='Measurements used for IOD')]
scene=dict(aspectmode='data',
          )
layout = dict(
    scene=scene)
fig = go.Figure(data=fig_data,
               layout=layout)
fig.show()
In [28]:
residuals_df = pd.DataFrame(columns=['bls_minus_measurements_norm', 'iod_minus_measurements_norm'])

for timestamp, pv_gcrf in position_GCRF.iterrows():    
    date_current = datetime_to_absolutedate(timestamp)
    
    pv_bls = bounded_propagator.getPVCoordinates(date_current, GCRF)
    pos_bls = np.array(pv_bls.getPosition().toArray())
    
    pv_iod = kepler_propagator_iod.getPVCoordinates(date_current, GCRF)
    pos_iod = np.array(pv_iod.getPosition().toArray())
    
    pv_measurements = position_GCRF.loc[timestamp]
    pos_measurements = pv_measurements[['x', 'y', 'z']]
    
    bls_minus_measurements = np.linalg.norm(pos_bls - pos_measurements)
    iod_minus_measurements = np.linalg.norm(pos_iod - pos_measurements)
    
    residuals_df.loc[timestamp] = [
        np.linalg.norm(pos_bls - pos_measurements),
        np.linalg.norm(pos_iod - pos_measurements)
    ]
    
display(residuals_df)
bls_minus_measurements_norm iod_minus_measurements_norm
2020-09-01 00:27:31.673 46296.309617 5.393430e+05
2020-09-01 00:48:52.204 46022.640331 5.209453e+05
2020-09-01 01:46:40.274 9244.643105 4.393567e+05
2020-09-01 02:21:12.153 44064.822355 4.175333e+05
2020-09-01 03:18:07.254 16103.763857 3.393861e+05
2020-09-01 03:47:52.086 45266.751618 3.243840e+05
2020-09-01 04:43:58.286 31552.073080 2.521959e+05
2020-09-01 05:20:31.552 37811.474729 2.285115e+05
2020-09-01 07:03:30.967 24471.733819 1.064424e+05
2020-09-01 07:59:16.286 9696.755913 5.873107e+04
2020-09-01 08:40:11.496 15238.715316 2.374416e-09
2020-09-01 10:04:50.842 14747.169446 1.277937e+05
2020-09-01 10:17:00.439 6900.635185 1.063508e+05
2020-09-01 11:19:48.314 6834.876701 2.348642e+05
2020-09-01 11:46:02.220 4042.886863 2.080264e+05
2020-09-01 11:55:01.713 2178.377460 2.144522e+05
2020-09-01 13:24:22.273 4499.469574 3.108564e+05
2020-09-01 14:27:02.274 5222.362403 4.249300e+05
2020-09-01 14:53:41.680 10210.061997 4.149235e+05
2020-09-01 16:01:24.231 4673.624566 5.263012e+05
2020-09-01 16:25:01.883 16943.822515 5.214621e+05
2020-09-01 17:53:31.235 21267.121318 6.301898e+05
2020-09-01 19:19:11.750 16123.709812 7.374010e+05
2020-09-01 19:37:50.403 31691.843467 7.281357e+05
2020-09-01 20:51:21.397 16077.124328 8.390159e+05
2020-09-01 21:01:21.947 34878.271915 8.359896e+05
2020-09-01 21:12:11.204 38790.266529 8.308779e+05
2020-09-01 21:18:50.992 31208.531725 8.314231e+05
2020-09-01 22:06:55.254 32228.528435 9.125086e+05
2020-09-01 22:27:51.017 25841.149089 9.412560e+05
2020-09-01 22:47:30.944 45198.094006 9.335931e+05
2020-09-01 23:41:02.319 37930.315420 1.015298e+06
In [30]:
fig = go.Figure(data=[
    go.Scatter(x=residuals_df.index, y=residuals_df['bls_minus_measurements_norm'], 
               name='BLS - measurements', 
               mode='markers+lines'),
    go.Scatter(x=residuals_df.index, y=residuals_df['iod_minus_measurements_norm'], 
               name='IOD - measurements', 
               mode='markers+lines')
])

fig.show()

Section # 11 - Export ephemeris data to OEM file in the CCSDS format

In [151]:
from datetime import datetime
# export ephemris data to OEM in the CCSDS format
def export_OEM(estimated_pv_df, date_list)->str:
    """Export ephemerides in CCSDS OEM format.
    Parameters
    ----------
    estimated_pv_df : Estimation results dataframe to export
    date_list : dates/times for each estimate datapoint
    Returns
    -------
    Ephemerides in OEM format.
    """

    # frame = Frame.ICRF if (cfg.prop_inertial_frame == Frame.GCRF) else cfg.prop_inertial_frame
    oem_header = f"""CCSDS_OEM_VERS = 2.0
CREATION_DATE = {datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")}
ORIGINATOR = PRIT CHOVATIYA

META_START
OBJECT_NAME      = DOVE by PLANET
OBJECT_ID        = 0f4e
CENTER_NAME      = EARTH
REF_FRAME        = EME2000
TIME_SYSTEM      = UTC
START_TIME       = {date_list[0]}
STOP_TIME        = {date_list[-1]}
META_STOP

"""

    eph_data = []

    for i in range(len(date_list)):
        utc = date_list[i] 
        pv_dat = estimated_pv_df.loc[absolutedate_to_datetime(utc)].astype(str)
        eph_data.append(f"{utc}\t\t{pv_dat[0][0:15]}\t\t{pv_dat[1][0:15]}\t\t{pv_dat[2][0:15]}\t\t{pv_dat[3][0:15]}\t\t{pv_dat[4][0:15]}\t\t{pv_dat[5][0:15]}")

    oem_data = oem_header + "\n".join(eph_data)
    return(oem_data)
In [142]:
with open("oem.txt", "w") as fp:
    fp.write(export_OEM(estimate_pv_EME2000, date_list)) # writing estimates ephem in EME2000 frame
fp.close()
In [ ]: