Frame Conversion ( True of Date to True of Epoch)


#1

Hello everyone,

I am trying to convert from TOD to TOE frame. The TOD frame is at the epoch of the orbit parameters but the TOE frame is at the same days of the orbit parameters but at 00h00.
So, TOE = TOD at the date of the orbit parameters but at 00h00.

What I would like to do is call the TOD frame but in a specific date.
I have tried with getFrozenFrame but it doesn’t works.

Do you know a way to create a specific Frame in a specific date?

Thank you in advance!


#2

Hi Nil,

Could you explain us a bit more what didn’t work with getFrozenFrame method ?
Because I think (I may be mistaken) that it was designed to do just what you want.

Max


#3

Hi Maxime,

I used the method getFrozenFrame with the next parameters:
reference - frame with respect to which the instance will be frozen = TOD Frame from Orekit FrameFactory
freezingDate - freezing date = TOE date (TOD date at 00h00)
frozenName - name of the frozen frame = “TOE”

But to call the method I have to use the object Frame which is also a TOD Frame from Orekit FrameFactory. (I guess that here is where the error comes from)

Then to check if it works I have an exemple (that you can find at the end), I convert keplerian parameters from TOE to TOD but the parameters are the same in both frames.

NB: If I use MOD or GCRF as reference frame for the getFrozen method, the parameters are not the same but they are not correct.

You can find the code used above:

    // Convert to TOE FRAME
    // TOD at the orbit epoch
    // TOE = TOD at the same orbit day but at 00h00       
    AbsoluteDate TOEDate = new AbsoluteDate(orbitDate.getYear(), orbitDate.getMonthValue(), orbitDate.getDayOfMonth(), 0, 0, 0,
        utc);
    Frame frameTOD = FramesFactory.getTOD(IERSConventions.IERS_2010, false);   
    Frame frameTOE = frameTOD.getFrozenFrame(frameTOD, TOEDate, "TOE");
    
    // Init Keplerian orbit 
    KeplerianOrbit kep_orbit = new KeplerianOrbit(a * 1000., e, toRadian(i), toRadian(ω), toRadian(RAAN), toRadian(v),
        PositionAngle.TRUE, frameTOE, orbitDate, mu);
    
    // Frame conversion to TOD    
    PVCoordinates pv_kep_tod = frameTOE.getTransformTo(FramesFactory.getTOD(IERSConventions.IERS_2010, true), orbitDate)
        .transformPVCoordinates(kep_orbit.getPVCoordinates());

TOE

Epoch 54785.35417
Date 2008/11/15 08:30:00.000

X -34810.22817 km
Y -23815.21387 km
Z -131.4846301 km
VX 1.737355059 km/sec
VY -2.533630295 km/sec
VZ 0.006340827 km/sec

Semi Major Axis 42120.170293 km
Eccentricity 0.001734235
Inclination 0.214110419 deg
RAAN 270.91234726 deg
Argument of perigee 85.115701741 deg
True anomaly 218.34952110 deg
Mean anomaly 218.47294956 deg
Mean Longitude 32.197545503 deg


TOD

Epoch 54785.35417
Date 2008/11/15 08:30:00.000

X -34810.23861 km
Y -23815.19863 km
Z -131.4796196 km
VX 1.737353951 km/sec
VY -2.533631056 km/sec
VZ 0.006340327 km/sec

Semi Major Axis 42120.170293 km
Eccentricity 0.001734235
Inclination 0.214099603 deg
RAAN 270.91339781 deg
Argument of perigee 85.114626119 deg
True anomaly 218.34952110 deg
Mean anomaly 218.47294956 deg
Mean Longitude 32.197545496 deg

Thank you,

Nil


#4

You should freeze your frame with respect to a frame considered “more inertial”, because
once frozen, the frame has the same motion as the reference frame.
So in your case, you should use either EM02000 or GCRF as the reference frame.
When using TOD as the reference frame, you basically say TOE doesn’t move with respect to TOD,
hence, you move with it.


#5

Hi Luc,

I already tried using EME2000 and GCRF but the output parameters (keplerian in TOD frame) are not the same as expected.

Another way to solve my problem would be to create the TOD frame at a precise epoch. Is that possible? Because when I initialize the TOD frame I don’t enter an epoch, however the frame is time dependent.

How does the TOD frame know the epoch if when I initialize the object I don’t introduce an epoch?

Thank you,

Nil


#6

I already tried using EME2000 and GCRF but the output parameters (keplerian in TOD frame)
are not the same as expected.

What did you try exactly? Could you post the code and say what ouput parameters you get and what you were expecting?

The proper way to get a TOE is really using getFrozenFrame as in

Frame frameTOE = frameTOD.getFrozenFrame(FramesFactory.getEME2000(), TOEDate, "TOE");

How does the TOD frame know the epoch if when I initialize the object I don’t introduce an epoch?

The TOD frame does not know the epoch. The time is taken into account when the getTransformTo method is called, so the Transform that is returned knows what time is it (it does implement the TimeStamped interface).

Frames are organized as a tree structure. Each frame knows its parent frame and has a way to compute the Transform between its parent and itself at any user specified date. The FramesFactory class simply predefines some frequently used frames, among which TOD, EME2000 and many more. Users can define new frames by just attaching them to a pre-existing frame and setting up a TransformProvider that will be used when getTransformTo walks the tree structure, gather the individual parent/child transforms at the specified date and combine them to provide a time-stamped global Transform going from frame A to frame B at date t.

TOD is one of the predefined frames, its parent frame is MOD, the parent frame of MOD is either EME2000 or GCRF depending on how EOP corrections are applied (when using the legacy IERS 1996 corrections, the fixed offset between EME2000 and GCRF is contained in the EOP data, so in order to avoid applying it twice, in this specific case MOD parent is GCRF), the parent frame of EME2000 is GCRF. The transform between TOD and its parent (MOD) corresponds to nutation effects, which are time dependent. The transform between MOD and its parent (EME2000 or GCRF) corresponds to precession effects which are time-dependent (and offset in the specific case mentioned above). The transform between EME2000 and GCRF is a time-independent offset.

If you want a TOE, it is not available as one of the predefined frames in the FramesFactory tree, so you have to build it. You need to select a “more inertial” frame to attach it to, and to set up a time-independent transform provider (i.e. something that always returns the same transform regardless of the date passed to it) that represents the transform between this selected parent frame and your TOE. As you want your frame to be fixed in time, you could either select TOD or MOD as a parent frame and “rewind” the motion of these frames between the epoch and current date. This is theoretically possible but would be difficult to do and would require computing precession and nutation twice. So a simpler approach is to select as the parent of TOE either EME2000 or GCRF. Suppose you select EME2000. The fixed transform you need is the transform from EME2000 to TOD at the epoch. You need to compute this by providing the date, and then store it to reuse it for other dates, not forgetting to reset all derivatives (rotation rate, rotation acceleration) to zero since it will now be considered a time-independent transform. You could do all these steps yourself, but these steps are exactly what getFrozenFrame does. The result of the call frameTOD.getFrozenFrame(FramesFactory.getEME2000(), TOEDate, "TOE") is a frame whose parent frame will be EME2000 (the first parameter to the call), linked to this parent by a time-independent transform, where this time-independent transform has been computed in such a way that it is exactly superimposed to the TOD frame at date TOEDate. So if you would call frameTOE.getTransformTo(frameTOD, date), then this transform would be the identity if date is exactly TOEDate, but would progressively drift away from identity as date drifts away from TOEDate, because the TOD frame continues its rotation due to precession and nutation whereas TOE frame doesn’t move as it has a fixed transform with respect to the fixed EME2000 frame.

Does this help?


#7

Another remark. In your code, when you compute the transform from TOE to TOD and apply this to your PVCoordinates, you could directly reuse the frameTOD variable instead of calling again he factory. You will get the exact same instance (the factory caches the frames it builds), but will avoid going through a map to retrieve the proper frame.
What is important to see is that the Frame class does not represents an orientation at some time, it represents the moving frame throughout the time line. The orientation at some time is the Transform returned by getTransformTo, not the Frame.


#8

Hi Luc,

Thank you for the explanation it was very helpful. I agree with you for the TOE frame creation, so I used

However, the results are not the same as expected. I don’t see where the error comes from now that I am doing the good transformation… maybe from another part?
You can find above the code and the test I used.

// Convert to TOE FRAME
    // TOD at the orbit epoch
    // TOE = TOD at the same orbit day but at 00h00 
    AbsoluteDate orbitDate = new AbsoluteDate(2008, 11, 15, 8, 30, 0, utc);        
    AbsoluteDate TOEDate = new AbsoluteDate(orbitDate.getYear(), orbitDate.getMonthValue(), orbitDate.getDayOfMonth(), 0, 0, 0,
        utc);
    Frame frameTOD = FramesFactory.getTOD(IERSConventions.IERS_2010, false);  
   //TOE Frame creation with J2000 
    Frame frameTOE = frameTOD.getFrozenFrame(FramesFactory.getEME2000(), TOEDate, "TOE");
    
    Vector3D position = new Vector3D(posx * 1000., posy * 1000., posz * 1000.);
    Vector3D velocity = new Vector3D(velx * 1000., vely * 1000., velz * 1000.);
    PVCoordinates pvCor = new PVCoordinates(position, velocity);
    CartesianOrbit car_orbit = new CartesianOrbit(pvCor, frame, orbitDate, mu);
    
    // Frame conversion to TOD    
    PVCoordinates pv_kep_tod = frameTOE.getTransformTo(FrameTOD, true), orbitDate)
        .transformPVCoordinates(car_orbit.getPVCoordinates());

For my test I used:
Input:
TOE
Epoch 54785.35417
Date 2008/11/15 08:30:00.000

X -34810.22817 km
Y -23815.21387 km
Z -131.4846301 km
VX 1.737355059 km/sec
VY -2.533630295 km/sec
VZ 0.006340827 km/sec

Expected output
TOD
Epoch 54785.35417
Date 2008/11/15 08:30:00.000

X -34810.23861 km
Y -23815.19863 km
Z -131.4796196 km
VX 1.737353951 km/sec
VY -2.533631056 km/sec
VZ 0.006340327 km/sec

Obtained output
TOD
Epoch 54785.35417
Date 2008/11/15 08:30:00.000

X -3.481021581141314E7 m
Y -2.3815231900256615E7 m
Z -131490.794709911 m
VX 1737.356607154262 m/sec
VY -2533.6297423366723 m/sec
VZ 6.341289236230928 m/sec

Thank you,

Nil


#9

Hi @Nil,

Do you know what is the reference date for the TOE frame in your input ?
I tried to freeze TOD at 2018-11-15 (the one you picked in your code) and 2018-11-16.
And the second one gives better compliance with your expected output.
Also, where did you get the expected output from ?

Cheers,
Maxime


#10

Hi Maxime,

The reference date for the TOE frame is always the parameters date at 00h00.
In the example I posted, the parameters date is 2008/11/15 08:30:00.000, which means that the TOE reference date is 2008/11/15 00:00:00.

I get the expected output from another code that has been tested.
I already ran another test (same code but different inputs and outputs) but it also failed. So, it is pretty sure that the problem doesn’t come from the awaited input.

Cheers,

Nil


#11

I tried to reproduce the problem and to check various frame changes, but was not able
to reproduce any results. The part that is worrying is that I do not even reproduce the
result you obtain from Orekit!

I updated my orekit-data folder to use the latest Earth Orientation Parameters, using IERS conventions 2010
for the frames. However, the EOP do not change anything here, even using only the data resources from
Orekit unit tests (which do not cover 2008-11-15) give the same result. This is because we are comparing
two very close frames on just a few hours, so EOP aplpy only as a differential effect on 8.5 hours.

In your code above, there is no need to use an intermediate Cartesian orbit (and anyway the frame argument in this orbit matches neither frameTOD nor frameTOE), you can use directly pvCor as
the argument to transformPVCoordinates. It seems you changed slightly Orekit API because there are no getYear(), getMonthValue(), getDayOfMonth() in AbsoluteDate, but I doubt there is a problem here. You could pass directly the literal constants 2018, 11, 15 in your test to rule out any problem there.

Here is my code, implemented as a Junit test that can be directly copied into TODProviderTests.java.

    @Test
    public void testTOE() {
        TimeScale      utc  =  TimeScalesFactory.getUTC();
        AbsoluteDate   date = new AbsoluteDate(2018, 11, 15, 8, 30, 0.0, utc);
        double posxTOE = -34810228.17;
        double posyTOE = -23815213.87;
        double poszTOE = -131484.6301;
        double velxTOE =  1737.355059;
        double velyTOE = -2533.630295;
        double velzTOE =  6.340827;
        double posxTOD = -34810238.61;
        double posyTOD = -23815198.63;
        double poszTOD = -131479.6196;
        double velxTOD =  1737.353951;
        double velyTOD = -2533.631056;
        double velzTOD =  6.340327;

        Frame          frameTOD  = FramesFactory.getTOD(IERSConventions.IERS_2010, true);  
        AbsoluteDate epoch = new AbsoluteDate(date.getComponents(utc).getDate(), TimeComponents.H00, utc);
        Frame frameTOE = frameTOD.getFrozenFrame(FramesFactory.getEME2000(), epoch, "TOE");
        
        PVCoordinates pvTOE = new PVCoordinates(new Vector3D(posxTOE, posyTOE, poszTOE),
                                                new Vector3D(velxTOE, velyTOE, velzTOE));
        PVCoordinates expected = new PVCoordinates(new Vector3D(posxTOD, posyTOD, poszTOD),
                                                   new Vector3D(velxTOD, velyTOD, velzTOD));

        // Frame conversion to TOD    
        PVCoordinates pvTOD = frameTOE.getTransformTo(frameTOD, date).transformPVCoordinates(pvTOE);
        System.out.format(Locale.US, "P: %13.3f %13.3f %13.3f%nV: %13.6f %13.6f %13.6f%n",
                          pvTOD.getPosition().getX(), pvTOD.getPosition().getY(), pvTOD.getPosition().getZ(),
                          pvTOD.getVelocity().getX(), pvTOD.getVelocity().getY(), pvTOD.getVelocity().getZ());

        Assert.assertEquals(0.0,
                            Vector3D.distance(expected.getPosition(), pvTOD.getPosition()),
                            0.001);
        Assert.assertEquals(0.0,
                            Vector3D.distance(expected.getVelocity(), pvTOD.getVelocity()),
                            0.001);
    }

The output I get is:
P: -34810223.026 -23815221.366 -131488.800
V: 1737.355596 -2533.629909 6.340868

This means I get something 29m away from your reference, but more worryingly still 12m away from the
results you get from Orekit, so I don’t understand.

Which version of Orekit do you use?


#12

Hi Luc,

I am using Orekit 9.1.

I think that the error comes from the date you set, in your code you set the date to 2018 and I set it to 2008 for my tests. In the examples I posted the date was correct but not in the code. I already change it.

To test the error, I ran my code with the date set to 2018 and I get the same output you get. I tried also with your code and I get the same output. So, I think the difference comes from the date.


#13

OK, with a date in 2008 and using the EOP at that date, I reproduce the same Orekit result as you,
and observe a 41.85m difference with the reference results.
If I switch the frames (i.e. considering the input is in TOD and the output in TOE), then the result
are much closer (3.57m difference), and changing the epoch to 01:21:05.500 instead of 00:00:00.000
reduces again the difference to about 22.5cm).
Could you double check a possible TOD/TOE inversion in your reference results?

Could you also provide us with a few more data points, with positions in the 3 frames TOD, TOE, EME2000
and dates at 3 or 4 different hours (including the exact 00:00:00.000 UTC hour) on two following days?


#14

I already considered a TOD/TOE inversion because the results were much closer but then I tried with another point and it didn’t work. So, it must be a coincidence.
For the data points, I only have two examples, the one I posted and this one:

Input:
TOE
Date 2013/07/26 19:25:00.000
Epoch 56499.80903 MJD

X 11060.02741 km
Y -365.73566 km
Z -675.15257 km
VX 4.4795909 km/s
VY 5.9524697 km/s
VZ -0.2859930 km/s

Expected output
TOD
Date 2013/07/26 19:25:00.000

X 11060.027277 km
Y -365.7378578 km
Z -675.1534678 km
VX 4.479591999 km/sec
VY 5.952468841 km/sec
VZ -0.285993880 km/sec

Semi Major Axis 24415.128323 km
Eccentricity 0.728320039
Inclination 3.499108079 deg
RAAN 91.926368956 deg
Argument of perigee 178.86617398 deg
True anomaly 87.320566227 deg
Mean anomaly 13.825818818 deg
Mean Longitude 102.26657265 deg


#15

I spent a few more hours looking at this but do not find anything wrong in our implementation.
I added the following statements in the test code:

        Rotation r0 = frameTOE.getTransformTo(FramesFactory.getEME2000(), date).getRotation();
        Rotation r1 = frameTOD.getTransformTo(FramesFactory.getEME2000(), epoch).getRotation();
        Rotation r2 = frameTOD.getTransformTo(FramesFactory.getEME2000(), date).getRotation();
        System.out.println(date);
        System.out.format(Locale.US, "r0 TOE -> EME2000:        %18.15f %18.15f %18.15f %18.15f%n",
                          r0.getQ0(), r0.getQ1(), r0.getQ2(), r0.getQ3());
        System.out.format(Locale.US, "r1 TOD(epoch) -> EME2000: %18.15f %18.15f %18.15f %18.15f%n",
                          r1.getQ0(), r1.getQ1(), r1.getQ2(), r1.getQ3());
        System.out.format(Locale.US, "r2 TOD(date)  -> EME2000: %18.15f %18.15f %18.15f %18.15f%n",
                          r2.getQ0(), r2.getQ1(), r2.getQ2(), r2.getQ3());
 
        System.out.format(Locale.US, "P TOE: %14.4f %14.4f %14.4f%n",
                          pvTOE.getPosition().getX(), pvTOE.getPosition().getY(), pvTOE.getPosition().getZ());
        System.out.format(Locale.US, "P TOD  = TOE.getTransform(TOD) (p TOE): %14.4f %14.4f %14.4f%n",
                          pvTOD.getPosition().getX(), pvTOD.getPosition().getY(), pvTOD.getPosition().getZ());
        Vector3D pEME2000 = r0.applyTo(pvTOE.getPosition());
        System.out.format(Locale.US, "P EME2000 = r0(P TOE): %14.4f %14.4f %14.4f%n",
                          pEME2000.getX(), pEME2000.getY(), pEME2000.getZ());
        Vector3D pTODBis = r2.applyInverseTo(pEME2000);
        System.out.format(Locale.US, "P TOD bis = r2^-1(P EME2000): %14.4f %14.4f %14.4f%n",
                          pTODBis.getX(), pTODBis.getY(), pTODBis.getZ());

These allow to track down to the most basic thing (3D rotations only), your test cases. Hare are the results I get, with IERS 2010 conventions and EOP from the latest finals20001.all file from IERS:

2008-11-15T08:30:00.000
r0 TOE -> EME2000:        -0.999999386362698 -0.000014694334147  0.000441454418156 -0.001015960776932
r1 TOD(epoch) -> EME2000: -0.999999386362698 -0.000014694334147  0.000441454418156 -0.001015960776932
r2 TOD(date)  -> EME2000: -0.999999386050600 -0.000014659565422  0.000441566711815 -0.001016219641631
P TOE: -34810228.1700 -23815213.8700   -131484.6301
P TOD  = TOE.getTransform(TOD) (p TOE): -34810215.8114 -23815231.9002   -131490.7947
P EME2000 = r0(P TOE): -34858649.1421 -23744436.3818   -100030.1197
P TOD bis = r2^-1(P EME2000): -34810215.8114 -23815231.9002   -131490.7947

and

2013-07-26T19:25:00.000
r0 TOE -> EME2000:        -0.999998579469317  0.000016809332261  0.000671638270993 -0.001545858605100
r1 TOD(epoch) -> EME2000: -0.999998579469317  0.000016809332261  0.000671638270993 -0.001545858605100
r2 TOD(date)  -> EME2000: -0.999998579313809  0.000016780989368  0.000671675324374 -0.001545943407975
P TOE:  11060027.4100   -365735.6600   -675152.5700
P TOD  = TOE.getTransform(TOD) (p TOE):  11060027.5220   -365733.7454   -675151.7721
P EME2000 = r0(P TOE):  11057926.9337   -399903.9909   -690020.7256
P TOD bis = r2^-1(P EME2000):  11060027.5220   -365733.7454   -675151.7721

So I extracted the pure rotations between TOE and EME2000 (r0), and between TOD and EME2000 at epoch (r1) and at date (r2). As expected, r0 and r1 are exactly the same, and r2 is slightly different (differential effect of precession and nutation during the day).

Then I converted the position, first using the complete TOE.getTransformTo(TOD) and then simply applying rotation r0 to the TOE position in order to get the position in EM2000 and then applying the inverse of rotation r2 to the position in EME2000 to retrieve position in TOD. I still get the exact same results.

I would advise you to check with your reference program the quaternions with respect to EM2000 and the EME2000 position also.

Orekit frames have been extensively validated by different teams and are known to be extremely accurate (centimeter level or better at Earth radius). The tests with the rotations above make me think the getFrozenFrame() underlying algorithm does what it is expected to do: it freezes the TOD/EME2000 rotation to a constant which corresponds to the freezing date, and then it applies it throughout timeline. I don’t see any errors here.


#16

Hi Luc,

After examining the source of the tests I have seen that the model used to produce the tests is not very accurate. There is an important difference between both models (Orekit vs Tests), which explains the difference found in the outputs.

Thank you for your help,

Nil