1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.frames;
18  
19  import org.hamcrest.MatcherAssert;
20  import org.hipparchus.geometry.euclidean.threed.Rotation;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.BeforeEach;
25  import org.junit.jupiter.api.Test;
26  import org.orekit.OrekitMatchers;
27  import org.orekit.Utils;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.time.TimeScalesFactory;
30  import org.orekit.utils.Constants;
31  import org.orekit.utils.IERSConventions;
32  
33  import java.util.stream.Stream;
34  
35  public class HelmertTransformationTest {
36  
37      @Test
38      public void testHelmert20052008() {
39          // for this test, we arbitrarily assume FramesFactory provides an ITRF 2008
40          Frame itrf2008 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
41          HelmertTransformation.Predefined ht = HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_2005;
42          Assertions.assertEquals(ITRFVersion.ITRF_2008, ht.getOrigin());
43          Assertions.assertEquals(ITRFVersion.ITRF_2005, ht.getDestination());
44          Assertions.assertEquals(new AbsoluteDate(2000, 1, 1, 12, 0, 0, TimeScalesFactory.getTT()),
45                              ht.getTransformation().getEpoch());
46          Frame itrf2005 = ht.createTransformedITRF(itrf2008, "2005");
47          Vector3D pos2005 = new Vector3D(1234567.8, 2345678.9, 3456789.0);
48  
49          // check the Helmert transformation as per http://itrf.ign.fr/ITRF_solutions/2008/tp_08-05.php
50          AbsoluteDate date = new AbsoluteDate(2005, 1, 1, 12, 0, 0, TimeScalesFactory.getTT());
51          Vector3D pos2008 = itrf2005.getTransformTo(itrf2008, date).transformPosition(pos2005);
52          Vector3D generalOffset = pos2005.subtract(pos2008);
53          Vector3D linearOffset  = computeOffsetLinearly(-0.5, -0.9, -4.7, 0.000, 0.000, 0.000,
54                                                          0.3,  0.0,  0.0, 0.000, 0.000, 0.000,
55                                                         pos2005, 0.0);
56          Vector3D error         = generalOffset.subtract(linearOffset);
57          Assertions.assertEquals(0.0, error.getNorm(), 2.0e-13 * pos2005.getNorm());
58          MatcherAssert.assertThat(
59                  itrf2005.getStaticTransformTo(itrf2008, date).transformPosition(pos2005),
60                  OrekitMatchers.vectorCloseTo(pos2008, 0));
61  
62          date = date.shiftedBy(Constants.JULIAN_YEAR);
63          pos2008 = itrf2005.getTransformTo(itrf2008, date).transformPosition(pos2005);
64          generalOffset = pos2005.subtract(pos2008);
65          linearOffset  = computeOffsetLinearly(-0.5, -0.9, -4.7, 0.000, 0.000, 0.000,
66                                                 0.3,  0.0,  0.0, 0.000, 0.000, 0.000,
67                                                pos2005, 1.0);
68          error         = generalOffset.subtract(linearOffset);
69          Assertions.assertEquals(0.0, error.getNorm(), 2.0e-13 * pos2005.getNorm());
70          MatcherAssert.assertThat(
71                  itrf2005.getStaticTransformTo(itrf2008, date).transformPosition(pos2005),
72                  OrekitMatchers.vectorCloseTo(pos2008, 0));
73  
74      }
75  
76      @Test
77      public void testHelmert20002005() {
78          // for this test, we arbitrarily assume FramesFactory provides an ITRF 2008
79          Frame itrf2008 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
80          Frame itrf2000 =
81                  HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_2000.createTransformedITRF(itrf2008, "2000");
82          Frame itrf2005 =
83                  HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_2005.createTransformedITRF(itrf2008, "2005");
84          Vector3D pos2000 = new Vector3D(1234567.8, 2345678.9, 3456789.0);
85  
86          // check the Helmert transformation as per http://itrf.ign.fr/ITRF_solutions/2005/tp_05-00.php
87          AbsoluteDate date = AbsoluteDate.J2000_EPOCH;
88          Vector3D pos2005 = itrf2000.getTransformTo(itrf2005, date).transformPosition(pos2000);
89          Vector3D generalOffset = pos2000.subtract(pos2005);
90          Vector3D linearOffset  = computeOffsetLinearly( 0.1, -0.8, -5.8, 0.000, 0.000, 0.000,
91                                                         -0.2,  0.1, -1.8, 0.000, 0.000, 0.000,
92                                                         pos2000, 0.0);
93          Vector3D error         = generalOffset.subtract(linearOffset);
94          Assertions.assertEquals(0.0, error.getNorm(), FastMath.ulp(pos2000.getNorm()));
95  
96          date = date.shiftedBy(Constants.JULIAN_YEAR);
97          pos2005 = itrf2000.getTransformTo(itrf2005, date).transformPosition(pos2000);
98          generalOffset = pos2000.subtract(pos2005);
99          linearOffset  = computeOffsetLinearly( 0.1, -0.8, -5.8, 0.000, 0.000, 0.000,
100                                                -0.2,  0.1, -1.8, 0.000, 0.000, 0.000,
101                                                pos2000, 1.0);
102         error         = generalOffset.subtract(linearOffset);
103         Assertions.assertEquals(0.0, error.getNorm(), FastMath.ulp(pos2000.getNorm()));
104 
105     }
106 
107     @Test
108     public void testHelmert19972000() {
109         // for this test, we arbitrarily assume FramesFactory provides an ITRF 2008
110         Frame itrf2008 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
111         Frame itrf2000 =
112                 HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_2000.createTransformedITRF(itrf2008, "2000");
113         Frame itrf97 =
114                 HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_1997.createTransformedITRF(itrf2008, "97");
115         Vector3D pos97 = new Vector3D(1234567.8, 2345678.9, 3456789.0);
116 
117         // check the Helmert transformation as per ftp://itrf.ensg.ign.fr/pub/itrf/ITRF.TP
118         AbsoluteDate date = new AbsoluteDate(1997, 1, 1, 12, 0, 0, TimeScalesFactory.getTT());
119         Vector3D pos2000 = itrf97.getTransformTo(itrf2000, date).transformPosition(pos97);
120         Vector3D generalOffset = pos97.subtract(pos2000);
121         Vector3D linearOffset  = computeOffsetLinearly( 6.7,  6.1, -18.5, 0.000, 0.000, 0.000,
122                                                         0.0, -0.6,  -1.4, 0.000, 0.000, 0.002,
123                                                        pos2000, 0.0);
124         Vector3D error         = generalOffset.subtract(linearOffset);
125         Assertions.assertEquals(0.0, error.getNorm(), 2.0e-11 * pos97.getNorm());
126 
127         date = date.shiftedBy(Constants.JULIAN_YEAR);
128         pos2000 = itrf97.getTransformTo(itrf2000, date).transformPosition(pos97);
129         generalOffset = pos97.subtract(pos2000);
130         linearOffset  = computeOffsetLinearly( 6.7,  6.1, -18.5, 0.000, 0.000, 0.000,
131                                                0.0, -0.6,  -1.4, 0.000, 0.000, 0.002,
132                                                pos2000, 1.0);
133         error         = generalOffset.subtract(linearOffset);
134         Assertions.assertEquals(0.0, error.getNorm(), 6.0e-11 * pos97.getNorm());
135 
136     }
137 
138     @Test
139     public void testHelmert19932000() {
140         // for this test, we arbitrarily assume FramesFactory provides an ITRF 2008
141         Frame itrf2008 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
142         Frame itrf2000 =
143                 HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_2000.createTransformedITRF(itrf2008, "2000");
144         Frame itrf93 =
145                 HelmertTransformation.Predefined.ITRF_2008_TO_ITRF_1993.createTransformedITRF(itrf2008, "93");
146         Vector3D pos93 = new Vector3D(1234567.8, 2345678.9, 3456789.0);
147 
148         // check the Helmert transformation as per ftp://itrf.ensg.ign.fr/pub/itrf/ITRF.TP
149         AbsoluteDate date = new AbsoluteDate(1988, 1, 1, 12, 0, 0, TimeScalesFactory.getTT());
150         Vector3D pos2000 = itrf93.getTransformTo(itrf2000, date).transformPosition(pos93);
151         Vector3D generalOffset = pos93.subtract(pos2000);
152         Vector3D linearOffset  = computeOffsetLinearly(12.7,  6.5, -20.9, -0.39,  0.80, -1.14,
153                                                        -2.9, -0.2,  -0.6, -0.11, -0.19,  0.07,
154                                                        pos2000, 0.0);
155         Vector3D error         = generalOffset.subtract(linearOffset);
156         Assertions.assertEquals(0.0, error.getNorm(), FastMath.ulp(pos93.getNorm()));
157 
158         date = date.shiftedBy(Constants.JULIAN_YEAR);
159         pos2000 = itrf93.getTransformTo(itrf2000, date).transformPosition(pos93);
160         generalOffset = pos93.subtract(pos2000);
161         linearOffset  = computeOffsetLinearly(12.7,  6.5, -20.9, -0.39,  0.80, -1.14,
162                                               -2.9, -0.2,  -0.6, -0.11, -0.19,  0.07,
163                                               pos2000, 1.0);
164         error         = generalOffset.subtract(linearOffset);
165         Assertions.assertEquals(0.0, error.getNorm(), FastMath.ulp(pos93.getNorm()));
166 
167     }
168 
169     @Test
170     public void test2020PivotVs2014Pivot() {
171         doTestPivot(2020, 2014);
172     }
173 
174     @Test
175     public void test2020PivotVs2008Pivot() {
176         doTestPivot(2020, 2008);
177     }
178 
179     @Test
180     public void test2014PivotVs2008Pivot() {
181         doTestPivot(2014, 2008);
182     }
183 
184     private void doTestPivot(final int year1, final int year2) {
185 
186         // for this test, we arbitrarily assume FramesFactory provides an ITRF year 1
187         Frame itrfPivot1 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
188         Frame itrfPivot2 = HelmertTransformation.Predefined.selectPredefined(year1, year2).
189                            createTransformedITRF(itrfPivot1, Integer.toString(year2));
190 
191         Stream.
192         of(HelmertTransformation.Predefined.values()).
193         filter(p -> p.getOrigin().getYear() == year1 && p.getDestination().getYear() != year2).
194         forEach(p1 -> {
195             HelmertTransformation.Predefined p2 =
196                             HelmertTransformation.Predefined.selectPredefined(year2, p1.getDestination().getYear());
197             if (p2 != null) {
198                 Frame itrfXFrom1 = p1.createTransformedITRF(itrfPivot1, "x-from-1");
199                 Frame itrfXFrom2 = p2.createTransformedITRF(itrfPivot2, "x-from-2");
200                 for (int year = 2000; year < 2007; ++year) {
201                     AbsoluteDate date = new AbsoluteDate(year, 4, 17, 12, 0, 0, TimeScalesFactory.getTT());
202                     Transform t = itrfXFrom2.getTransformTo(itrfXFrom1, date);
203                     // the errors are not strictly zero (but they are very small) because
204                     // Helmert transformations are a translation plus a rotation. If we do
205                     // t1 -> r1 -> t2 -> r2, it is not the same as t1 -> t2 -> r1 -> r2
206                     // which would correspond to simply add the offsets, velocities, rotations and rate,
207                     // which is what is done in the reference documents. Anyway, the non-commutativity
208                     // errors are well below models accuracy
209                     Assertions.assertEquals(0, t.getTranslation().getNorm(),  6.0e-6);
210                     Assertions.assertEquals(0, t.getVelocity().getNorm(),     2.0e-22);
211                     Assertions.assertEquals(0, t.getRotation().getAngle(),    2.0e-12);
212                     Assertions.assertEquals(0, t.getRotationRate().getNorm(), 2.0e-32);
213                     final StaticTransform st = itrfXFrom2.getStaticTransformTo(itrfXFrom1, date);
214                     MatcherAssert.assertThat(st.getTranslation(),
215                                              OrekitMatchers.vectorCloseTo(t.getTranslation(), 0));
216                     MatcherAssert.assertThat(Rotation.distance(st.getRotation(), t.getRotation()),
217                                              OrekitMatchers.closeTo(0, 0));
218                 }
219             }
220         });
221     }
222 
223     private Vector3D computeOffsetLinearly(final double t1,    final double t2,    final double t3,
224                                            final double r1,    final double r2,    final double r3,
225                                            final double t1Dot, final double t2Dot, final double t3Dot,
226                                            final double r1Dot, final double r2Dot, final double r3Dot,
227                                            final Vector3D p,   final double dt) {
228         double t1U = (t1 + dt * t1Dot) * 1.0e-3;
229         double t2U = (t2 + dt * t2Dot) * 1.0e-3;
230         double t3U = (t3 + dt * t3Dot) * 1.0e-3;
231         double r1U = FastMath.toRadians((r1 + dt * r1Dot) / 3.6e6);
232         double r2U = FastMath.toRadians((r2 + dt * r2Dot) / 3.6e6);
233         double r3U = FastMath.toRadians((r3 + dt * r3Dot) / 3.6e6);
234         return new Vector3D(t1U - r3U * p.getY() + r2U * p.getZ(),
235                             t2U + r3U * p.getX() - r1U * p.getZ(),
236                             t3U - r2U * p.getX() + r1U * p.getY());
237     }
238 
239     @BeforeEach
240     public void setUp() {
241         Utils.setDataRoot("compressed-data");
242     }
243 
244 }