Kalman filtering, TDM fabrication

Hello, I have an issue with using a propagator with a Kalman filter, where I input TDM data (which I essentially fabricate myself).

I’ve set it up like this:

  • I have SP3 data (in the ECEF reference frame) every 5 minutes.
  • I initialize the initial state using a TLE propagator.
  • I use numerical propagator with perturbations
  • I update the Kalman filter using data generated by this code (ICRS, ICRF):
from astropy.coordinates import EarthLocation, SkyCoord, CartesianRepresentation
from astropy import units as u
from astropy.time import Time
import numpy as np

X = propagated_position.getX()  # Współrzędna X w metrach
Y = propagated_position.getY()  # Współrzędna Y w metrach
Z = propagated_position.getZ()  # Współrzędna Z w metrach

eci_coords = CartesianRepresentation(X * u.m, Y * u.m, Z * u.m)

from astropy.time import Time

time = Time('2024-01-20T02:15:32.321332', format='isot', scale='utc')

print(time)

skycoord = SkyCoord(eci_coords, frame='icrs', obstime=time)

ra = skycoord.ra
dec = skycoord.dec

print(f"(RA): {ra}")
print(f"(Dec): {dec}")

Below are the key fragments related to the Kalman filter. I’d like to ask whether this approach is correct for using RA/DEC data because, so far, the discrepancies are significant.

  • Propagation alone allows errors within a few meters over a 5-minute horizon.
  • Using the Kalman filter this way, the errors grow to hundreds of kilometers! :cry:
integrator_builder = DormandPrince853IntegratorBuilder(min_step, max_step, position_tolerance)
propagator_builder = NumericalPropagatorBuilder(
    initial_state.getOrbit(), integrator_builder, PositionAngleType.MEAN, position_tolerance
)

cov_vel=30.0
cov_pos=100.0
covariance_matrix = Array2DRowRealMatrix(6, 6)
covariance_matrix.setEntry(0, 0, cov_pos)  # X
covariance_matrix.setEntry(1, 1, cov_pos)  # Y
covariance_matrix.setEntry(2, 2, cov_pos)  # Z
covariance_matrix.setEntry(3, 3, cov_vel)   # VX
covariance_matrix.setEntry(4, 4, cov_vel)   # VY
covariance_matrix.setEntry(5, 5, cov_vel)   # VZ

process_noise = ConstantProcessNoise(covariance_matrix, covariance_matrix)
kalman_builder = KalmanEstimatorBuilder()
kalman_builder.addPropagationConfiguration(propagator_builder, process_noise)
kalman_estimator = kalman_builder.build()

for index, observation in enumerate(observations):
    if(index<130):
        utc = TimeScalesFactory.getUTC()
        date_str, ra, dec = observation
        date = parse_date(date_str, utc)
        print(date_str, ra, dec)
        ra_rad = math.radians(ra)
        dec_rad = math.radians(dec)
    
        # Tworzenie pomiaru kątowego    
        angular_measurement = AngularRaDec(
            ground_station, frame, date, [ra_rad, dec_rad], [2.0, 2.0], [1.0, 1.0], observable_satellite
        )
    
        kalman_estimator.estimationStep(angular_measurement)
        estimated_state = kalman_estimator.getPhysicalEstimatedState()

I tried different matrixes settings also

I’d like to point out that when I fed SP3 data into the Kalman filter, the error was very small (after all, I was comparing it against the same data used for estimation). However, I can’t achieve the same level of accuracy when using RA/DEC data.

Any guidance or insights on whether this methodology is valid would be greatly appreciated. Thank you! :rocket:

Hi,

Can you show us more about:

  • the settings of the integrator builder
  • the propagation frame (if your initial state comes from TlePropagator, then it’s TEME and you should use another one)
  • the ground station definition
  • the frame given as the observation one

Have you tried giving very small covariances for your RA Dec ?

Last remark, your process noise which is rather large in velocity could make the filter diverge since it is applied every 5 minutes.

Cheers,
Romain.

Tell me more please

I tried different settings for covariances




line1 = "1 37867U 20001A   24342.45052083  .00000000  00000-0  00000-0 0  9999"
line2 = "2 37867  64.9673  91.7134 0002336 232.9463 142.7921  2.13101429    05"

tle = TLE(line1, line2)

tle_propagator = TLEPropagator.selectExtrapolator(tle)
frame = FramesFactory.getEME2000()
initial_state = tle_propagator.getInitialState()

initial_position = initial_state.getPVCoordinates().getPosition()
initial_velocity = initial_state.getPVCoordinates().getVelocity()

earth = OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                         Constants.WGS84_EARTH_FLATTENING,
                         FramesFactory.getITRF(IERSConventions.IERS_2010, True))
longitude = math.radians(20.810862)
latitude = math.radians(-32.379944)
altitude = 1798.0

station_position = GeodeticPoint(latitude, longitude, altitude)
station_frame = TopocentricFrame(earth, station_position, "Esrange")
ground_station = GroundStation(station_frame)

min_step = 0.1  
max_step = 300.0  
position_tolerance = 1.0 

integrator_builder = DormandPrince853IntegratorBuilder(min_step, max_step, position_tolerance)
propagator_builder = NumericalPropagatorBuilder(
    initial_state.getOrbit(), integrator_builder, PositionAngleType.MEAN, position_tolerance
)

cov_vel=30.0
cov_pos=100.0
covariance_matrix = Array2DRowRealMatrix(6, 6)
covariance_matrix.setEntry(0, 0, cov_pos)  # X
covariance_matrix.setEntry(1, 1, cov_pos)  # Y
covariance_matrix.setEntry(2, 2, cov_pos)  # Z
covariance_matrix.setEntry(3, 3, cov_vel)   # VX
covariance_matrix.setEntry(4, 4, cov_vel)   # VY
covariance_matrix.setEntry(5, 5, cov_vel)   # VZ

process_noise = ConstantProcessNoise(covariance_matrix, covariance_matrix)
kalman_builder = KalmanEstimatorBuilder()
kalman_builder.addPropagationConfiguration(propagator_builder, process_noise)
kalman_estimator = kalman_builder.build()

observable_satellite = ObservableSatellite(0)
errors = []


for index, observation in enumerate(observations):
    if(index<130):
        utc = TimeScalesFactory.getUTC()
        date_str, ra, dec = observation
        date = parse_date(date_str, utc)
        print(date_str, ra, dec)
        ra_rad = math.radians(ra)
        dec_rad = math.radians(dec)
    
        # Tworzenie pomiaru kątowego    
        angular_measurement = AngularRaDec(
            ground_station, frame, date, [ra_rad, dec_rad], [2.0, 2.0], [1.0, 1.0], observable_satellite
        )
    
        kalman_estimator.estimationStep(angular_measurement)
        estimated_state = kalman_estimator.getPhysicalEstimatedState()
        error = sqrt(
            (position_eci.getX() - ref_x[index])**2 +
            (position_eci.getY() - ref_y[index])**2 +
            (position_eci.getZ() - ref_z[index])**2
        )
        errors.append(error)
    


Please explain to me what reference frame an observation station requires, because if we have the ICRF, why do we need to provide the station’s coordinates if this frame is independent of the observer?

The propagation frame is inferred by the builder from the orbit you pass. So you need to create a new CartesianOrbit with EME2000 for example, by using the getPVCoordinates method (with one argument) on your state coming from the TLE.

The position tolerance you use for the integrator is too large. Use 0.001 or less. You might also want to decrease your min step.

Your ground station definition seems okay.
However, the frame you pass to AngularRaDec must be coherent with how you created your TDM. If it was ICRF you used with astropy, use it here too.

One last thing, is your orekit data and more particularly your Earth Orientation Parameters up to date? Run the update.sh to be sure.

Edit: I don’t see where you add the perturbations to your propagator builder. Is it just somewhere you’ve not pasted here?
And what is your reference for position?

Cheers,
Romain.