Hello Romain, 
Thank you for your quick response!
I’ve implemented your suggestion of working in a .py
file. Additionally, I switched to using Starlink ephemeris data, which helped in simplifying the process by eliminating laser ranging data for now.
To configure various propagatorBuilder
options with different force model configurations, I created a function as shown below:
def configure_force_models(propagatorBuilder,cr, cd, cross_section, enable_gravity=True, enable_third_body=True,
enable_solar_radiation=True, enable_relativity=True, enable_atmospheric_drag=True):
# Earth gravity field with degree 64 and order 64
if enable_gravity:
itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, False)
ecef = itrf
gravityProvider = GravityFieldFactory.getNormalizedProvider(64, 64)
gravityAttractionModel = HolmesFeatherstoneAttractionModel(ecef, gravityProvider)
propagatorBuilder.addForceModel(gravityAttractionModel)
# Moon and Sun perturbations
if enable_third_body:
moon = CelestialBodyFactory.getMoon()
sun = CelestialBodyFactory.getSun()
moon_3dbodyattraction = ThirdBodyAttraction(moon)
sun_3dbodyattraction = ThirdBodyAttraction(sun)
propagatorBuilder.addForceModel(moon_3dbodyattraction)
propagatorBuilder.addForceModel(sun_3dbodyattraction)
# Solar radiation pressure
if enable_solar_radiation:
itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, False)
ecef = itrf
wgs84Ellipsoid = ReferenceEllipsoid.getWgs84(ecef)
cross_section = float(cross_section)
cr = float(cr)
isotropicRadiationSingleCoeff = IsotropicRadiationSingleCoefficient(cross_section, cr)
solarRadiationPressure = SolarRadiationPressure(sun, wgs84Ellipsoid, isotropicRadiationSingleCoeff)
propagatorBuilder.addForceModel(solarRadiationPressure)
# Relativity
if enable_relativity:
relativity = Relativity(orekit_constants.EIGEN5C_EARTH_MU)
propagatorBuilder.addForceModel(relativity)
# Atmospheric drag
if enable_atmospheric_drag:
itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, False)
ecef = itrf
wgs84Ellipsoid = ReferenceEllipsoid.getWgs84(ecef)
msafe = MarshallSolarActivityFutureEstimation(
MarshallSolarActivityFutureEstimation.DEFAULT_SUPPORTED_NAMES,
MarshallSolarActivityFutureEstimation.StrengthLevel.AVERAGE)
atmosphere = DTM2000(msafe, sun, wgs84Ellipsoid)
isotropicDrag = IsotropicDrag(float(cross_section), float(cd))
dragForce = DragForce(atmosphere, isotropicDrag)
propagatorBuilder.addForceModel(dragForce)
return propagatorBuilder
Then, I followed more or less the same process as earlier (except using PV
instead of Range
and AngularAzEl
):
def main():
ephem_path = '/Users/charlesc/Documents/GitHub/ERP_tools/external/ephems/starlink/MEME_57632_STARLINK-30309_3530645_Operational_1387262760_UNCLASSIFIED.txt'
spacex_ephem_dfwcov = spacex_ephem_to_df_w_cov(ephem_path)
sat_list = {
'STARLINK-30309': {
'norad_id': 57632, # For Space-Track TLE queries
'cospar_id': '2023-122A', # For laser ranging data queries
'sic_id': '000', # For writing in CPF files
'mass': 800.0, # kg; v2 mini
'cross_section': 100.0, # m2; TODO: get proper value
'cd': 1.5, # TODO: compute proper value
'cr': 1.0 # TODO: compute proper value
}
}
sc_name = 'STARLINK-30309' # Change the name to select a different satellite in the dict
odDate = datetime(2023, 12, 19, 6, 45, 42, 00000)
collectionDuration = 1 * 1/24 * 1/60 * 5 # 5 minutes
startCollectionDate = odDate + timedelta(days=-collectionDuration)
#Get TLE for first guess
# Space-Track
identity_st = input('Enter SpaceTrack username')
password_st = getpass.getpass(prompt='Enter SpaceTrack password for account {}'.format(identity_st))
st = SpaceTrackClient(identity=identity_st, password=password_st)
rawTle = st.tle(norad_cat_id=sat_list[sc_name]['norad_id'], epoch='<{}'.format(odDate), orderby='epoch desc', limit=1, format='tle')
tleLine1 = rawTle.split('\n')[0]
tleLine2 = rawTle.split('\n')[1]
print(tleLine1)
print(tleLine2)
# Orbit propagator parameters
prop_min_step = 0.001 # s
prop_max_step = 300.0 # s
prop_position_error = 5.0 # m
# Estimator parameters
estimator_position_scale = 1.0 # m
estimator_convergence_thres = 1e-2
estimator_max_iterations = 25
estimator_max_evaluations = 35
# tod = FramesFactory.getTOD(IERSConventions.IERS_2010, False) # Taking tidal effects into account when interpolating EOP parameters
gcrf = FramesFactory.getGCRF()
# Selecting frames to use for OD
eci = gcrf
# utc = TimeScalesFactory.getUTC()
# mjd_utc_epoch = AbsoluteDate(1858, 11, 17, 0, 0, 0.0, utc)
orekitTle = TLE(tleLine1, tleLine2)
itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, False)
ecef = itrf
wgs84Ellipsoid = ReferenceEllipsoid.getWgs84(ecef)
nadirPointing = NadirPointing(eci, wgs84Ellipsoid)
sgp4Propagator = SGP4(orekitTle, nadirPointing, sat_list[sc_name]['mass'])
tleInitialState = sgp4Propagator.getInitialState()
# tleEpoch = tleInitialState.getDate()
tleOrbit_TEME = tleInitialState.getOrbit()
tlePV_ECI = tleOrbit_TEME.getPVCoordinates(eci)
tleOrbit_ECI = CartesianOrbit(tlePV_ECI, eci, wgs84Ellipsoid.getGM())
integratorBuilder = DormandPrince853IntegratorBuilder(prop_min_step, prop_max_step, prop_position_error)
propagatorBuilder = NumericalPropagatorBuilder(tleOrbit_ECI,
integratorBuilder, PositionAngleType.MEAN, estimator_position_scale)
propagatorBuilder.setMass(sat_list[sc_name]['mass'])
propagatorBuilder.setAttitudeProvider(nadirPointing)
propagatorBuilders = []
configurations = [
{'enable_gravity': True, 'enable_third_body': False, 'enable_solar_radiation': False, 'enable_relativity': False, 'enable_atmospheric_drag': False},
{'enable_gravity': True, 'enable_third_body': True, 'enable_solar_radiation': False, 'enable_relativity': False, 'enable_atmospheric_drag': False},
{'enable_gravity': True, 'enable_third_body': True, 'enable_solar_radiation': True, 'enable_relativity': False, 'enable_atmospheric_drag': False},
{'enable_gravity': True, 'enable_third_body': True, 'enable_solar_radiation': True, 'enable_relativity': True, 'enable_atmospheric_drag': False},
{'enable_gravity': True, 'enable_third_body': True, 'enable_solar_radiation': True, 'enable_relativity': True, 'enable_atmospheric_drag': True},
]
for config in configurations:
propagatorBuilder = NumericalPropagatorBuilder(tleOrbit_ECI, integratorBuilder, PositionAngleType.MEAN, estimator_position_scale)
propagatorBuilder.setMass(sat_list[sc_name]['mass'])
propagatorBuilder.setAttitudeProvider(nadirPointing)
configured_propagatorBuilder = configure_force_models(propagatorBuilder,sat_list[sc_name]['cr'], sat_list[sc_name]['cd'],
sat_list[sc_name]['cross_section'], **config)
propagatorBuilders.append(configured_propagatorBuilder)
results = {}
# Outer loop over each propagatorBuilder (/force model configuration)
for idx, propagatorBuilder in enumerate(propagatorBuilders):
print(f"Running for configuration {idx}")
print(f"propagatorBuilder: {propagatorBuilder}")
print("allforcemodels: ", propagatorBuilder.getAllForceModels())
matrixDecomposer = QRDecomposer(1e-7)
optimizer = GaussNewtonOptimizer(matrixDecomposer, False)
estimator = BatchLSEstimator(optimizer, propagatorBuilder)
print(f"estimator: {estimator}")
estimator.setParametersConvergenceThreshold(estimator_convergence_thres)
estimator.setMaxIterations(estimator_max_iterations)
estimator.setMaxEvaluations(estimator_max_evaluations)
# Try different number of points to use to use in OD process
for points_to_use in range(1, 150, 45):
print(f"Running for {points_to_use} points with configuration {idx}")
observableSatellite = ObservableSatellite(0)
for _, row in spacex_ephem_dfwcov.head(points_to_use).iterrows():
date = datetime_to_absolutedate((row['UTC']).to_pydatetime())
position = Vector3D(row['x']*1000, row['y']*1000, row['z']*1000)
velocity = Vector3D(row['u']*1000, row['v']*1000, row['w']*1000)
sigmaPosition = row['sigma_pos']
sigmaVelocity = row['sigma_vel']
baseWeight = 1.0
orekitPV = PV(date, position, velocity, sigmaPosition, sigmaVelocity, baseWeight, observableSatellite)
estimator.addMeasurement(orekitPV)
estimatedPropagatorArray = estimator.estimate()
date_start = datetime_to_absolutedate(startCollectionDate).shiftedBy(-86400.0)
date_end = datetime_to_absolutedate(odDate).shiftedBy(86400.0)
estimatedPropagator = estimatedPropagatorArray[0]
estimatedInitialState = estimatedPropagator.getInitialState()
actualOdDate = estimatedInitialState.getDate()
estimatedPropagator.resetInitialState(estimatedInitialState)
estimatedgenerator = estimatedPropagator.getEphemerisGenerator()
estimatedPropagator.propagate(date_start, date_end)
bounded_propagator = estimatedgenerator.getGeneratedEphemeris()
lvlh = LocalOrbitalFrame(eci, LOFType.LVLH, bounded_propagator, 'LVLH')
covMat_eci_java = estimator.getPhysicalCovariances(1.0e-10)
eci2lvlh_frozen = eci.getTransformTo(lvlh, actualOdDate).freeze()
jacobianDoubleArray = JArray_double2D(6, 6)
eci2lvlh_frozen.getJacobian(CartesianDerivativesFilter.USE_PV, jacobianDoubleArray)
jacobian = Array2DRowRealMatrix(jacobianDoubleArray)
covMat_lvlh_java = jacobian.multiply(covMat_eci_java.multiply(jacobian.transpose()))
# covarianceMat_eci = np.matrix([covMat_eci_java.getRow(iRow)
# for iRow in range(0, covMat_eci_java.getRowDimension())])
covarianceMat_lvlh = np.matrix([covMat_lvlh_java.getRow(iRow)
for iRow in range(0, covMat_lvlh_java.getRowDimension())])
Here are the covariance matrices that I get when running the 5 different force models with 46 PV
in the OD. As you can see there is some change- which is better than what I was getting earlier. I’m now just not sure whether this is a reasonable change to expect or if I may still have the same problem as earlier.





Thanks again for your help and expertise!
Best,
Charles