Thank you very much @MaximeJ !
All of your suggestions worked! I have also added some force models, but TBH I didn’t notce any significant changes in estimated orbits (I’m working with LEO satellites with altitudes <1000km so I feel like I did something wrong).
Right now I’m dealing with another problem that I don’t know if it is easily resolvable. I’m working mainly with pretty short (but dense) arcs (for example I have ~100 measurments for 20 sec arc) and I don’t know if this is enough to estimate orbit with enough accuracy.
In my data I’ve found one satellite that I have observations of in 2 consecutive nights. So I performed OD on measurments from first night and propagated it to the next night. Unfortunately results from my propagation are way off compared to measurments:
Red dots are measurments used for orbit determination (blue lines). Green dots are measurments from next night, purple dots are calculated positions for next night.
Code that im working on:
from org.orekit.frames import FramesFactory
from org.orekit.utils import IERSConventions
gcrf = FramesFactory.getGCRF()
itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, False)
from org.orekit.time import TimeScalesFactory
utc = TimeScalesFactory.getUTC()
from org.orekit.models.earth import ReferenceEllipsoid
wgs84Ellipsoid = ReferenceEllipsoid.getWgs84(itrf)
from org.orekit.bodies import CelestialBodyFactory
moon = CelestialBodyFactory.getMoon()
sun = CelestialBodyFactory.getSun()
Transforming data from itrf to gcrf (I feel like there is more efficient and pythonic way):
from org.orekit.estimation.measurements import Position
import math
i = math.ceil(len(dane.index) / 2)
points_for_iod = dane.iloc[[0, i, -1]]
from org.hipparchus.geometry.euclidean.threed import Vector3D
from orekit.pyhelpers import datetime_to_absolutedate
from org.orekit.utils import PVCoordinates
pos_1 = points_for_iod.iloc[0]
p_vector_1 = Vector3D(pos_1[['x_', 'y_', 'z_']].to_list())
v_vector_1 = Vector3D(pos_1[["vx_", "vy_", "vz_"]].to_list())
date_1 = datetime_to_absolutedate(points_for_iod.index[0])
tmp_pv1 = PVCoordinates(p_vector_1, v_vector_1)
pv1 = itrf.getTransformTo(gcrf, date_1).transformPVCoordinates(tmp_pv1)
pos_2 = points_for_iod.iloc[1]
p_vector_2 = Vector3D(pos_2[['x_', 'y_', 'z_']].to_list())
v_vector_2 = Vector3D(pos_2[["vx_", "vy_", "vz_"]].to_list())
date_2 = datetime_to_absolutedate(points_for_iod.index[1])
tmp_pv2 = PVCoordinates(p_vector_2, v_vector_2)
pv2 = itrf.getTransformTo(gcrf, date_2).transformPVCoordinates(tmp_pv2)
pos_3 = points_for_iod.iloc[2]
p_vector_3 = Vector3D(pos_3[['x_', 'y_', 'z_']].to_list())
v_vector_3 = Vector3D(pos_3[["vx_", "vy_", "vz_"]].to_list())
date_3 = datetime_to_absolutedate(points_for_iod.index[2])
tmp_pv3 = PVCoordinates(p_vector_3, v_vector_3)
pv3 = itrf.getTransformTo(gcrf, date_3).transformPVCoordinates(tmp_pv3)
Performing IOD:
from org.orekit.estimation.iod import IodGibbs
from org.orekit.utils import Constants as orekit_constants
iod_gibbs = IodGibbs(orekit_constants.EIGEN5C_EARTH_MU)
initialOrbit = iod_gibbs.estimate(gcrf,
pv1.getPosition(), date_1,
pv2.getPosition(), date_2,
pv3.getPosition(), date_3)
display(initialOrbit)
#<KeplerianOrbit: Keplerian parameters: {a: 5665948.126048018; e: 0.2763193273172972; i: 81.17959707499644; pa: -44.55326269968; raan: 119.82447080790052; v: 178.94313046625413;}>
Setting up estimator and propagator:
# Estimator parameters
estimator_position_scale = 1.0 # m
estimator_convergence_thres = 1e-2
estimator_max_iterations = 25
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
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, OrbitType
initialCartOrbit = OrbitType.CARTESIAN.convertType(initialOrbit)
propagatorBuilder = NumericalPropagatorBuilder(initialCartOrbit,
integratorBuilder,
PositionAngle.TRUE,
estimator_position_scale)
from org.orekit.forces.gravity.potential import GravityFieldFactory
gravityProvider = GravityFieldFactory.getConstantNormalizedProvider(64, 64)
from org.orekit.forces.gravity import HolmesFeatherstoneAttractionModel
gravityAttractionModel = HolmesFeatherstoneAttractionModel(itrf, gravityProvider)
propagatorBuilder.addForceModel(gravityAttractionModel)
from org.orekit.forces.gravity import ThirdBodyAttraction
moon_3dbodyattraction = ThirdBodyAttraction(moon)
propagatorBuilder.addForceModel(moon_3dbodyattraction)
sun_3dbodyattraction = ThirdBodyAttraction(sun)
propagatorBuilder.addForceModel(sun_3dbodyattraction)
from org.orekit.forces.radiation import IsotropicRadiationSingleCoefficient
isotropicRadiationSingleCoeff = IsotropicRadiationSingleCoefficient(100.0, 1.0);
from org.orekit.forces.radiation import SolarRadiationPressure
solarRadiationPressure = SolarRadiationPressure(sun, wgs84Ellipsoid.getEquatorialRadius(),
isotropicRadiationSingleCoeff)
propagatorBuilder.addForceModel(solarRadiationPressure)
from org.orekit.models.earth.atmosphere.data import MarshallSolarActivityFutureEstimation
from orekit import JArray
msafe = MarshallSolarActivityFutureEstimation(
'(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\p{Digit}\p{Digit}\p{Digit}\p{Digit}f10\.(?:txt|TXT)',
MarshallSolarActivityFutureEstimation.StrengthLevel.AVERAGE)
DM.feed(msafe.getSupportedNames(), msafe)
from org.orekit.models.earth.atmosphere import NRLMSISE00
atmosphere = NRLMSISE00(msafe, sun, wgs84Ellipsoid)
from org.orekit.forces.drag import IsotropicDrag
isotropicDrag = IsotropicDrag(100.0, 1.0)
from org.orekit.forces.drag import DragForce
dragForce = DragForce(atmosphere, isotropicDrag)
propagatorBuilder.addForceModel(dragForce)
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)
Adding measurments to the estimator and estimating:
from org.orekit.estimation.measurements import Position, ObservableSatellite
observableSatellite = ObservableSatellite(0) # Propagator index = 0
for timestamp, pv_gcrf in dane.iterrows():
tmp_p = Vector3D(pv_gcrf[['x_', 'y_', 'z_']].to_list())
tmp_v = Vector3D(pv_gcrf[['vx_', 'vy_', 'vz_']].to_list())
tmp_pvc = PVCoordinates(tmp_p, tmp_v)
pvc = itrf.getTransformTo(gcrf, datetime_to_absolutedate(timestamp)).transformPVCoordinates(tmp_pvc)
orekit_position = Position(
datetime_to_absolutedate(timestamp),
pvc.getPosition(),
1000.0,
1.0, # Base weight
observableSatellite
)
estimator.addMeasurement(orekit_position)
estimatedPropagatorArray = estimator.estimate()
Getting estimated orbit:
from org.orekit.orbits import KeplerianOrbit
estimatedPropagator = estimatedPropagatorArray[0]
estimatedInitialState = estimatedPropagator.getInitialState()
tmp_orbit = OrbitType.KEPLERIAN.convertType(estimatedInitialState.getOrbit())
orbit = KeplerianOrbit.cast_(tmp_orbit)
display(orbit)
#<KeplerianOrbit: Keplerian parameters: {a: 7129764.816986412; e: 0.01604576521836157; i: 81.19281051652376; pa: -18.340653585441842; raan: 119.8104073176335; v: 152.7333235809643;}>
Setting up bounded propagator:
dt = 60.0
date_start = datetime_to_absolutedate(dane.index[0])
date_end = datetime_to_absolutedate(dane.index[-1]).shiftedBy(86400.0)
ephmerisGenerator = estimatedPropagator.getEphemerisGenerator()
estimatedPropagator.propagate(date_start, date_end)
boundedPropagator = ephmerisGenerator.getGeneratedEphemeris()
Propagating to the next night, saving results to “pos_df”:
from orekit.pyhelpers import absolutedate_to_datetime
pos_df = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])
date_current = date_start
while date_current.compareTo(date_end) <= 0:
datetime_current = absolutedate_to_datetime(date_current)
spacecraftState = boundedPropagator.propagate(date_current)
pv = spacecraftState.getPVCoordinates(itrf)
pos = np.array(pv.getPosition().toArray())
pos_df.loc[datetime_current] = np.concatenate(
(pos,
np.array(pv.getVelocity().toArray()))
)
date_current = date_current.shiftedBy(dt)
Calculating next night position in roughy same times as my measurments (“dane2” is dataframe with measurments from the next night):
pos_df_next = pd.DataFrame(columns=['x', 'y', 'z', 'vx', 'vy', 'vz'])
date_start = datetime_to_absolutedate(dane2.index[0])
date_end = datetime_to_absolutedate(dane2.index[-1])
dt = 0.2
display(date_start, date_end)
date_current = date_start
while date_current.compareTo(date_end) <= 0:
datetime_current = absolutedate_to_datetime(date_current)
spacecraftState = boundedPropagator.propagate(date_current)
pv = spacecraftState.getPVCoordinates(itrf)
pos = np.array(pv.getPosition().toArray())
pos_df_next.loc[datetime_current] = np.concatenate(
(pos,
np.array(pv.getVelocity().toArray()))
)
date_current = date_current.shiftedBy(dt)
Plotting (plot uploaded above code):
import plotly.graph_objects as go
fig_data = data=[
go.Scatter3d(x=pos_df['x'], y=pos_df['y'], z=pos_df['z'],
mode='lines',
name='Batch least squares solution'),
go.Scatter3d(x=dane['x_'], y=dane['y_'], z=dane['z_'],
mode='markers',
name='Measurements'),
go.Scatter3d(x=dane2['x_'], y=dane2['y_'], z=dane2['z_'],
mode='markers',
name='Measurements (next day)'),
go.Scatter3d(x=pos_df_next['x'], y=pos_df_next['y'], z=pos_df_next['z'],
mode='markers',
name='Batch least squares solution (next day)'),
]
scene=dict(
aspectmode='data',
)
layout = dict(
scene=scene
)
fig = go.Figure(
data=fig_data,
layout=layout)
fig.show()
Is there something really wrong with this code or it’s just not possible to determine accurate enough orbit with such short arcs?
