# Topocentric Position to Geocentric Frame Conversion

Hi all,

I am new to this topic and Orekit. I am trying perform IOD using the IodGibbs method. From my understanding this method can take the following call: estimate(Frame frame, Position p1, Position p2, Position p3)

I have right ascension and declination measurements of a satellite and the latitude, longitude and altitude of the ground station. The radec measurements are from the ground station and therefore taken from a topocentric frame. As seen in the code below I use the radec measurement to create a vector of the observation and am trying to create a Vector3D position in the ECI frame so that I can input the position for 3 observations into the iodGibbs function. Could I please get some validation on whether this method of doing a frame conversion is correct? I seem to be getting coordinates that are in the same order of magnitude of the actual J2000 coordinates of the satellite at the specific epoch that I am testing… but the numbers are still fairly off as shown below.

``````utc_time_scale = TimeScalesFactory.getUTC()

ECI = FramesFactory.getEME2000()  # Define ECI reference frame.
ECEF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)  # Define ECEF reference frame.

earth = OneAxisEllipsoid(
Constants.WGS84_EARTH_FLATTENING,
ECEF,
)

def create_sat_position(ra, dec, lat, lon, alt, rng, earth: OneAxisEllipsoid, date: AbsoluteDate):
range_in_m = rng * 1000
alt_in_m = alt * 1000

# Create the topocentric frame of the observer
observer_point = GeodeticPoint(lat, lon, alt_in_m)
observer_topo_frame = TopocentricFrame(earth, observer_point, "Observer")

# Convert Ra and Dec to a direction vector in the topocentric frame
sat_direction_topo = Vector3D(
cos(dec) * cos(ra),
cos(dec) * sin(ra),
sin(dec)
)
# Transform observer's position to ECEF frame
observer_position_ecef = earth.transform(observer_point)

# Transform observer's position from ECEF frame to ECI frame.
ecef_to_eci_transform = FramesFactory.getITRF(IERSConventions.IERS_2010, True).getStaticTransformTo(ECI, date)
observer_position_eci = ecef_to_eci_transform.transformPosition(observer_position_ecef)

# Transform direction from topocentric to ECI frame
topocentric_to_eci_transform = observer_topo_frame.getStaticTransformTo(ECI, date)
satellite_direction_eci = topocentric_to_eci_transform.transformVector(sat_direction_topo)

# Scale the direction vector by the range
satellite_relative_position_eci = satellite_direction_eci.scalarMultiply(range_in_m)

# Combine observer's ECEF position with the satellite's relative position

# The satellite's position in ECEF coordinates

print("Satellite Position in ECI (X, Y, Z):", satellite_position_eci)
g = input()

Position = create_sat_position(
327.850223,
36.185382,
38.994194,
-104.6365,
2.237,
1120.4,
earth,
AbsoluteDate(2023, 6, 23, 4, 46, 38.674272, utc_time_scale),
)
``````

I am expecting a position output of X=-1873.775, Y = -4683.677, Z = 4658.770 (km) but I am getting:

Satellite Position in ECI (X, Y, Z): {-2,421,665.6076448183; -5,302,058.939778156; 4,041,127.0943075754}

Hi and welcome to the forum,

I believe you’ll find a solution there.

Cheers,
Romain.

Hi @Serrof.

Thank you for your reply. I tried to implement what was tagged as the solution in the link that you sent. I tried to implement it as shown below. I am simply trying to get a position vector right now in the ECI (J2000) frame. I am still getting values that are off. Here is my code:

``````# create a timescale object for UTC time
utc_time_scale = TimeScalesFactory.getUTC()

# initialise our different frames
ECI = FramesFactory.getEME2000()  # Define ECI reference frame.
ECEF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)  # Define ECEF reference frame.

# we need a BodyShape object to reference when we make our Topocentric Frame
earth = OneAxisEllipsoid(
Constants.WGS84_EARTH_FLATTENING,
ECEF,
)

def create_sat_position(ra, dec, lat, lon, alt, rng, earth: OneAxisEllipsoid, date: AbsoluteDate):
"""
Returns 'Position' of observation in ECI frame.
"""
range_in_m = rng * 1000
alt_in_m = alt * 1000

# Create the topocentric frame of the observer
groundStationPoint = GeodeticPoint(lat, lon, alt_in_m)
groundStationFrame = TopocentricFrame(earth, groundStationPoint, "Observer")

# Convert Ra and Dec to a direction vector in the topocentric frame
sat_direction_topo = Vector3D(
range_in_m * cos(dec) * cos(ra),
range_in_m * cos(dec) * sin(ra),
range_in_m * sin(dec)
)

transform = groundStationFrame.getTransformTo(earth.getBodyFrame(), date)

# Transform RADEC to ECI coordinates
pvECI = groundStationFrame.getTransformTo(earth.getBodyFrame(), date).transformPVCoordinates(PVCoordinates(sat_direction_topo,
Vector3D.ZERO)); #Velocity is assumed to be zero in this example

print(pvECI.getPosition().scalarMultiply(1/1000))

position = create_sat_position(
327.850223,
36.185382,
38.994194,
-104.6365,
2.237,
1120.4,
earth,
AbsoluteDate(2023, 6, 23, 4, 46, 38.674272, utc_time_scale),
)
``````

I am concerned that the frame conversion is still not correct which might be what is causing this disparity. I am still confident that the date, time and measurement values are all accurately inputted into the function call. For your reference:

Expected Result: X=-1873.775, Y = -4683.677, Z = 4658.770 (km)

After implementing the solution outlined in your suggestion, I am getting:

X = -720.3461726758, Y = -5,788.190605978, Z = 4,035.4618581839

I would appreciate any advice. Thank you.

Hi there,

You’re transforming to ECEF here, not ECI. Just before your call to `transformPVCoordinates`, the call to `getTransformTo` must use the inertial frame as input, not earth.getBodyFrame.

NB: to build your `Vector3D`, you could do `Vector3D(ra, dec).scalarMultiply(range_in_m)`

Cheers,
Romain.

Hi @Serrof

I really appreciate your advice. I am still however facing issues where the output is not accurate after performing the frame conversion. I have taken your last advice into account and used the inertial frame (ECI) as the input.

``````def create_sat_position(ra, dec, lat, lon, alt, rng, earth: OneAxisEllipsoid, date: AbsoluteDate):
"""
Returns 'Position' of observation in ECI frame.
"""
range_in_m = rng * 1000
alt_in_m = alt * 1000

# Create the topocentric frame of the observer
groundStationPoint = GeodeticPoint(lat, lon, alt_in_m)
groundStationFrame = TopocentricFrame(earth, groundStationPoint, "Observer")

# Convert Ra and Dec to a direction vector in the topocentric frame
sat_direction_topo = Vector3D(ra, dec).scalarMultiply(range_in_m)

# Transform RADEC to ECI coordinates
pvECI = groundStationFrame.getTransformTo(ECI, date).transformPVCoordinates(
PVCoordinates(sat_direction_topo, Vector3D.ZERO)
)  # Velocity is assumed to be zero in this example
``````

I am using the same test and am getting the old result I was getting in my first query:

Expected: X=-1873.775, Y = -4683.677, Z = 4658.770 (km)
Result: X = -2,421,665.6076448183 Y = -5,302,058.939778156, Z = 4,041,127.0943075754 (km)

Could this be an issue with the Python wrapper? I am not sure where I am going wrong with the frame conversion or how to further troubleshoot the problem. I am confident that the expected result is correct and the inputs are correctly applied. Thank you once again for your help.

I guess your ra and dec stand for right ascension and declination. If this is the case, the `sat_direction_topo` vector computed is not expressed in `groundStationFrame` but in some intermediate frame centered at ground station but oriented along the axes of inertial frame. A `TopoCentricFrame` orientation is:

• X axis in the local horizontal plane (normal to zenith direction) and following the local parallel towards East
• Y axis in the horizontal plane (normal to zenith direction) and following the local meridian towards North
• Z axis towards Zenith direction

`station_satellite_inert = Vector3D(ra, dec).scalarMultiply(range_in_m)` will give you a station-to-satellite vector in inertial frame. You should add the earth-center-to-station vector in inertial frame to it to get the vector you need in inertial frame. You don’t need the full `groundStationFrame`, only the position of its origin (something like `earth_station_inert = groundStationFrame.getTransformTo(ECI, date).transformPosition(Vector3D.ZERO)` and then `p1 = earth_station_inert.add(station_satellite_inert)`.

My bad, the link I gave you @panik was for converting AzEl, which are in a topocentric frame with “geodetic” axes. As Luc pointed out, topocentric RaDec usually use inertially oriented axes.

Cheers,
Romain

Thank you @luc for this detailed clarification and explanation. This seems to fix my problem. Very much appreciated.