TLE propagation does not match experimental observations

Dear Orekit community,
I am trying to set up a pipeline to estimate an orbit with ranges obtained from satellite laser ranging with a TLE as an initial guess. For this a TLE of TOPEX/Poseidon is used to generate a CPF-file, which then allows ranges to the satellite being measured by the SLR-station Zimmerwald. For this measuring session the TLE of that day was quite accurate and measurements could be obtained.
When trying to visualize the tracking geometry I noticed the following problem: The satellite positions generated from the TLEPropagator appear to be below the horizon of the ground station while the positions generated from the measurements form a sensible arc above it.

Looking at the two arcs, it looks like something might be wrong with the timestamps, but I already made sure that all times are given in UTC. What am I doing wrong here? The code to reproduce the problem and my measurements are below.

Thanks for any ideas and cheers,
Pascal

import pandas as pd
import numpy as np
import plotly.graph_objects as go

import orekit
orekit.initVM()
from orekit.pyhelpers import setup_orekit_curdir, download_orekit_data_curdir, datetime_to_absolutedate
# download_orekit_data_curdir("../orekit-data.zip")
setup_orekit_curdir("../orekit-data.zip")
from org.orekit.time import AbsoluteDate, TimeScalesFactory
from org.orekit.propagation.analytical.tle import TLE, TLEPropagator
from org.orekit.attitudes import NadirPointing
from org.orekit.frames import FramesFactory, TopocentricFrame
from org.orekit.utils import IERSConventions
from org.orekit.utils import Constants
from org.orekit.bodies import OneAxisEllipsoid, GeodeticPoint

topex_df = pd.read_csv("data/topex_slr_normal_points_small.csv")
topex_df['TIMESTAMP'] = pd.to_datetime(topex_df['TIMESTAMP'])
absolute_dates = [datetime_to_absolutedate(ts) for ts in topex_df['TIMESTAMP']]

ITRF = FramesFactory.getITRF(IERSConventions.IERS_2010, False)
EME2000 = FramesFactory.getEME2000()
earth = OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                         Constants.WGS84_EARTH_FLATTENING,
                         ITRF)

lat = float(np.deg2rad(46.8772))
lon = float(np.deg2rad(7.4652))
alt = 951.2
name = "Zimmerwald"
point = GeodeticPoint(lat, lon, alt)
zwd_topo_frame = TopocentricFrame(earth, point, name)

lat = float(np.deg2rad(46.8772))
lon = float(np.deg2rad(7.4652))
alt = 951.2
name = "Zimmerwald"
point = GeodeticPoint(lat, lon, alt)
zwd_topo_frame = TopocentricFrame(earth, point, name)

# TOPEX/POSEIDON          
line1 = "1 22076U 92052A   25335.73517493 -.00000028  00000-0  14489-3 0  9992"
line2 = "2 22076  66.0382 182.4050 0007747 271.8090 157.2908 12.81088102558197"
mass_topex = 2675.9  # kg from DISCOS
tle = TLE(line1, line2)
attitude_provider = NadirPointing(EME2000, earth) # real object is tumbling
tle_prop = TLEPropagator.selectExtrapolator(tle, attitude_provider, mass_topex)
# Equivalent results to TLEPropagator.selectExtrapolator(tle)

ok_vecs = [cpf_propagator.propagate(t).getPosition(EME2000) for t in absolute_dates]
X = [v.getX() for v in ok_vecs]
Y = [v.getY() for v in ok_vecs]
Z = [v.getZ() for v in ok_vecs]

station_pos = zwd_topo_frame.getPosition(absolute_dates[0], EME2000)
x_station = station_pos.getX()
y_station = station_pos.getY()
z_station = station_pos.getZ()

def RAzEl(ranges, azimuths, elevations, timestamps, frame=EME2000):
    """Convert range, azimuth, elevation to position vectors in given frame."""
    pos_vectors = []
    for range, azimuth, elevation, timestamp in zip(ranges, azimuths,
                                                    elevations, timestamps):
        sat_geo_point = zwd_topo_frame.pointAtDistance(
            float(np.deg2rad(azimuth)),
            float(np.deg2rad(elevation)),
            range)
        sat_topo = TopocentricFrame(earth, sat_geo_point, "Satellite_Pos")
        sat_vector3d = sat_topo.getPosition(timestamp, frame)
        sat_vector_pos = np.array([sat_vector3d.getX(),
                                   sat_vector3d.getY(),
                                   sat_vector3d.getZ()])
        pos_vectors.append(sat_vector_pos)
    return pos_vectors

sat_est_positions = RAzEl(topex_df["RANGE"],
                          topex_df["AZI"],
                          topex_df["ELE"],
                          absolute_dates)
x_meas, y_meas, z_meas = zip(*sat_est_positions)

# Plot TLE orbit
fig = go.Figure(data=[go.Scatter3d(x=X, y=Y, z=Z,
                                   name='TOPEX/POSEIDON Orbit',
                                   mode='lines+markers',
                                   line=dict(color='blue', width=2),
                                   marker=dict(size=2, color='red'))])
# Plot measured positions from RAEZL
fig.add_trace(go.Scatter3d(x=x_meas, y=y_meas, z=z_meas,
                           mode='lines+markers',
                           line=dict(color='green', width=2),
                           marker=dict(size=2, color='orange'),
                           name='Estimated Positions from RAEZL'))
# Plot ground station position
fig.add_trace(go.Scatter3d(x=[x_station], y=[y_station], z=[z_station],
                           mode='markers',
                           marker=dict(size=5, color='purple'),
                           name='Ground Station Zimmerwald'))

theta = np.linspace(0, 2 * np.pi, 100)
phi = np.linspace(0, np.pi, 100)

# Set up coordinates for points on the sphere
R_E = 6368.134 * 1000
x0 = R_E * np.outer(np.cos(theta), np.sin(phi))
y0 = R_E * np.outer(np.sin(theta), np.sin(phi))
z0 = R_E * np.outer(np.ones(100), np.cos(phi))

# Set up surface plot for Earth
fig.add_trace(go.Surface(x=x0, y=y0, z=z0,
                         hoverinfo="none",
                         colorscale='Blues',
                         contours_x_highlight=False,
                         contours_y_highlight=False,
                         contours_z_highlight=False,
                         showscale=False))

fig.update_layout(title="TOPEX/POSEIDON Orbit from TLE Propagator and Measured Positions",
                  scene=dict(xaxis_title='X (m)',
                             yaxis_title='Y (m)',
                             zaxis_title='Z (m)'),
                  width=800,
                  height=800)
fig.show()

topex_slr_normal_points_small.csv (1.8 KB)

Hi Pascal,

Could this be the problem: you’re using an inertial frame to get the station’s position, but your plots are in Earth-fixed no?

Cheers,
Romain.

1 Like

Hi @Serrof,
Thanks for the quick suggestion. I hope I understood you correctly. Changing all the instances of EME2000 to ITRF (except in nadir pointing) rotates the problem but the same discrepancy remains.

Cheers,
Pascal

It seems that your TLE propagating part is correct. And the results from the propagator is true that the satellite should be below the horizon.

you should check the csv file, i.e. topex_slr_normal_points_small.csv. There could be something wrong.

1 Like

Hello @xzguo,
I think you are right. I double checked the TLE propagation in skyfield and it matches the orekit prediction. I found a discrepancy between the laser ranging timestamps and our tracking camera: The tracking camera shows timestamps between 16:19 and 16:27, which matches a pass of the TLE. Therefore, the fault must be with my experimental setup and not with Orekit.
Thanks for the help,
Pascal

2 Likes