Commit 8ed1a5ef authored by Luc Maisonobe's avatar Luc Maisonobe
Browse files

Added support for ITRF-2020.

Fixes #918
parent d01619f6
......@@ -21,6 +21,9 @@
</properties>
<body>
<release version="11.2" date="TBD" description="TBD">
<action dev="luc" type="add" issue="918">
Added support for ITRF-2020.
</action>
<action dev="maxime" type="fix" issue="909">
Fixed wrong implementation of NTW LOF frame.
</action>
......
......@@ -120,6 +120,22 @@ public enum CelestialBodyFrame {
},
/** International Terrestrial Reference Frame 2020. */
ITRF2020 {
/** {@inheritDoc} */
@Override
public Frame getFrame(final IERSConventions conventions,
final boolean simpleEOP,
final DataContext dataContext) {
if (conventions == null) {
throw new OrekitException(OrekitMessages.CCSDS_UNKNOWN_CONVENTIONS);
}
return dataContext.getFrames().getITRF(ITRFVersion.ITRF_2020, conventions, simpleEOP);
}
},
/** International Terrestrial Reference Frame 2014. */
ITRF2014 {
......
......@@ -16,6 +16,9 @@
*/
package org.orekit.frames;
import java.util.Optional;
import java.util.stream.Stream;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.geometry.euclidean.threed.FieldRotation;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
......@@ -53,11 +56,111 @@ import org.orekit.utils.PVCoordinates;
public class HelmertTransformation implements TransformProvider {
/** serializable UID. */
private static final long serialVersionUID = -1900615992141291146L;
private static final long serialVersionUID = 20220419L;
/** Enumerate for predefined Helmert transformations. */
public enum Predefined {
// see https://itrf.ign.fr/docs/solutions/itrf2020/Transfo-ITRF2020_TRFs.txt
// SOLUTION Tx Ty Tz D Rx Ry Rz EPOCH
// UNITS----------> mm mm mm ppb .001" .001" .001"
// . . . . . . .
// RATES Tx Ty Tz D Rx Ry Rz
// UNITS----------> mm/y mm/y mm/y ppb/y .001"/y .001"/y .001"/y
// -----------------------------------------------------------------------------------------
// ITRF2014 -1.4 -0.9 1.4 -0.42 0.00 0.00 0.00 2015.0
// rates 0.0 -0.1 0.2 0.00 0.00 0.00 0.00
// ITRF2008 0.2 1.0 3.3 -0.29 0.00 0.00 0.00 2015.0
// rates 0.0 -0.1 0.1 0.03 0.00 0.00 0.00
// ITRF2005 2.7 0.1 -1.4 0.65 0.00 0.00 0.00 2015.0
// rates 0.3 -0.1 0.1 0.03 0.00 0.00 0.00
// ITRF2000 -0.2 0.8 -34.2 2.25 0.00 0.00 0.00 2015.0
// rates 0.1 0.0 -1.7 0.11 0.00 0.00 0.00
// ITRF97 6.5 -3.9 -77.9 3.98 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF96 6.5 -3.9 -77.9 3.98 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF94 6.5 -3.9 -77.9 3.98 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF93 -65.8 1.9 -71.3 4.47 -3.36 -4.33 0.75 2015.0
// rates -2.8 -0.2 -2.3 0.12 -0.11 -0.19 0.07
// ITRF92 14.5 -1.9 -85.9 3.27 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF91 26.5 12.1 -91.9 4.67 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF90 24.5 8.1 -107.9 4.97 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF89 29.5 32.1 -145.9 8.37 0.00 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// ITRF88 24.5 -3.9 -169.9 11.47 0.10 0.00 0.36 2015.0
// rates 0.1 -0.6 -3.1 0.12 0.00 0.00 0.02
// _________________________________________________________________________________________
/** Transformation from ITRF 2020 To ITRF 2014. */
ITRF_2020_TO_ITRF_2014(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_2014, 2015,
-1.4, -0.9, 1.4, 0.00, 0.00, 0.00,
0.0, -0.1, 0.2, 0.00, 0.00, 0.00),
/** Transformation from ITRF 2020 To ITRF 2008. */
ITRF_2020_TO_ITRF_2008(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_2008, 2015,
0.2, 1.0, 3.3, 0.00, 0.00, 0.00,
0.0, -0.1, 0.1, 0.00, 0.00, 0.00),
/** Transformation from ITRF 2020 To ITRF 2005. */
ITRF_2020_TO_ITRF_2005(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_2005, 2015,
2.7, 0.1, -1.4, 0.00, 0.00, 0.00,
0.3, -0.1, 0.1, 0.00, 0.00, 0.00),
/** Transformation from ITRF 2020 To ITRF 2000. */
ITRF_2020_TO_ITRF_2000(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_2000, 2015,
-0.2, 0.8, -34.2, 0.00, 0.00, 0.00,
0.1, 0.0, -1.7, 0.00, 0.00, 0.00),
/** Transformation from ITRF 2020 To ITRF 97. */
ITRF_2020_TO_ITRF_1997(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1997, 2015,
6.5, -3.9, -77.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 96. */
ITRF_2020_TO_ITRF_1996(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1996, 2015,
6.5, -3.9, -77.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 94. */
ITRF_2020_TO_ITRF_1994(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1994, 2015,
6.5, -3.9, -77.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 93. */
ITRF_2020_TO_ITRF_1993(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1993, 2015,
-65.8, 1.9, -71.3, -3.36, -4.33, 0.75,
-2.8, -0.2, -2.3, -0.11, -0.19, 0.07),
/** Transformation from ITRF 2020 To ITRF 92. */
ITRF_2020_TO_ITRF_1992(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1992, 2015,
14.5, -1.9, -85.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 91. */
ITRF_2020_TO_ITRF_1991(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1991, 2015,
26.5, 12.1, -91.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 90. */
ITRF_2020_TO_ITRF_1990(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1990, 2015,
24.5, 8.1, -107.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 89. */
ITRF_2020_TO_ITRF_1989(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1989, 2015,
29.5, 32.1, -145.9, 0.00, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
/** Transformation from ITRF 2020 To ITRF 88. */
ITRF_2020_TO_ITRF_1988(ITRFVersion.ITRF_2020, ITRFVersion.ITRF_1988, 2015,
24.5, -3.9, -169.9, 0.10, 0.00, 0.36,
0.1, -0.6, -3.1, 0.00, 0.00, 0.02),
// see http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2014_ITRFs.txt
// SOLUTION Tx Ty Tz D Rx Ry Rz EPOCH
// UNITS----------> mm mm mm ppb .001" .001" .001"
......@@ -103,8 +206,8 @@ public class HelmertTransformation implements TransformProvider {
/** Transformation from ITRF 2014 To ITRF 2000. */
ITRF_2014_TO_ITRF_2000(ITRFVersion.ITRF_2014, ITRFVersion.ITRF_2000, 2010,
0.7, 1.2, -26.1, 0.00, 0.00, 0.00,
0.1, 0.1, -1.9, 0.00, 0.00, 0.00),
0.7, 1.2, -26.1, 0.00, 0.00, 0.00,
0.1, 0.1, -1.9, 0.00, 0.00, 0.00),
/** Transformation from ITRF 2014 To ITRF 97. */
ITRF_2014_TO_ITRF_1997(ITRFVersion.ITRF_2014, ITRFVersion.ITRF_1997, 2010,
......@@ -341,6 +444,21 @@ public class HelmertTransformation implements TransformProvider {
return new Frame(parent, getTransformation(tt), name);
}
/** Select a predefined transform between two years.
* @param origin origin year
* @param destination destination year
* @return predefined transform from origin to destination, or null if no such predefined transform exist
* @since 11.2
*/
public static Predefined selectPredefined(final int origin, final int destination) {
final Optional<HelmertTransformation.Predefined> optional =
Stream.
of(HelmertTransformation.Predefined.values()).
filter(p -> p.getOrigin().getYear() == origin && p.getDestination().getYear() == destination).
findFirst();
return optional.isPresent() ? optional.get() : null;
}
}
/**
......
......@@ -36,6 +36,9 @@ import org.orekit.time.TimeScale;
*/
public enum ITRFVersion {
/** Constant for ITRF 2020. */
ITRF_2020(2020),
/** Constant for ITRF 2014. */
ITRF_2014(2014),
......@@ -147,6 +150,20 @@ public enum ITRFVersion {
}
/** Get last supported ITRF version.
* @return last supported ITRF version
* @since 11.2
*/
public static ITRFVersion getLast() {
ITRFVersion last = ITRFVersion.ITRF_1988;
for (final ITRFVersion iv : ITRFVersion.values()) {
if (iv.getYear() > last.getYear()) {
last = iv;
}
}
return last;
}
/** Find a converter between specified ITRF frames.
*
* <p>This method uses the {@link DataContext#getDefault() default data context}.
......@@ -186,9 +203,10 @@ public enum ITRFVersion {
}
if (provider == null) {
// no direct provider found, use ITRF 2014 as a pivot frame
provider = TransformProviderUtils.getCombinedProvider(getDirectTransformProvider(origin, ITRF_2014, tt),
getDirectTransformProvider(ITRF_2014, destination, tt));
// no direct provider found, use last supported ITRF as a pivot frame
final ITRFVersion last = getLast();
provider = TransformProviderUtils.getCombinedProvider(getDirectTransformProvider(origin, last, tt),
getDirectTransformProvider(last, destination, tt));
}
// build the converter, to keep the origin and destination information
......
......@@ -17,6 +17,8 @@
package org.orekit.frames;
import java.util.stream.Stream;
import org.hamcrest.MatcherAssert;
import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
......@@ -166,21 +168,39 @@ public class HelmertTransformationTest {
}
@Test
public void test2014PivotVs2008Pivot() {
public void test2020PivotVs2014Pivot() {
doTestPivot(2020, 2014);
}
// for this test, we arbitrarily assume FramesFactory provides an ITRF 2014
Frame itrf2014 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
Frame itrf2008 = HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_2008.createTransformedITRF(itrf2014, "2008");
@Test
public void test2020PivotVs2008Pivot() {
doTestPivot(2020, 2008);
}
@Test
public void test2014PivotVs2008Pivot() {
doTestPivot(2014, 2008);
}
for (final HelmertTransformation.Predefined p2008 : HelmertTransformation.Predefined.values()) {
if (p2008.toString().startsWith("ITRF_2008_TO")) {
HelmertTransformation.Predefined p2014 =
HelmertTransformation.Predefined.valueOf(p2008.toString().replaceAll("2008", "2014"));
Frame itrfXFrom2008 = p2008.createTransformedITRF(itrf2008, "x-from-2008");
Frame itrfXFrom2014 = p2014.createTransformedITRF(itrf2014, "x-from-2014");
private void doTestPivot(final int year1, final int year2) {
// for this test, we arbitrarily assume FramesFactory provides an ITRF year 1
Frame itrfPivot1 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
Frame itrfPivot2 = HelmertTransformation.Predefined.selectPredefined(year1, year2).
createTransformedITRF(itrfPivot1, Integer.toString(year2));
Stream.
of(HelmertTransformation.Predefined.values()).
filter(p -> p.getOrigin().getYear() == year1 && p.getDestination().getYear() != year2).
forEach(p1 -> {
HelmertTransformation.Predefined p2 =
HelmertTransformation.Predefined.selectPredefined(year2, p1.getDestination().getYear());
if (p2 != null) {
Frame itrfXFrom1 = p1.createTransformedITRF(itrfPivot1, "x-from-1");
Frame itrfXFrom2 = p2.createTransformedITRF(itrfPivot2, "x-from-2");
for (int year = 2000; year < 2007; ++year) {
AbsoluteDate date = new AbsoluteDate(year, 4, 17, 12, 0, 0, TimeScalesFactory.getTT());
Transform t = itrfXFrom2014.getTransformTo(itrfXFrom2008, date);
Transform t = itrfXFrom2.getTransformTo(itrfXFrom1, date);
// the errors are not strictly zero (but they are very small) because
// Helmert transformations are a translation plus a rotation. If we do
// t1 -> r1 -> t2 -> r2, it is not the same as t1 -> t2 -> r1 -> r2
......@@ -191,16 +211,14 @@ public class HelmertTransformationTest {
Assert.assertEquals(0, t.getVelocity().getNorm(), 2.0e-22);
Assert.assertEquals(0, t.getRotation().getAngle(), 2.0e-12);
Assert.assertEquals(0, t.getRotationRate().getNorm(), 2.0e-32);
final StaticTransform st = itrfXFrom2014.getStaticTransformTo(itrfXFrom2008, date);
MatcherAssert.assertThat(
st.getTranslation(),
OrekitMatchers.vectorCloseTo(t.getTranslation(), 0));
MatcherAssert.assertThat(
Rotation.distance(st.getRotation(), t.getRotation()),
OrekitMatchers.closeTo(0, 0));
final StaticTransform st = itrfXFrom2.getStaticTransformTo(itrfXFrom1, date);
MatcherAssert.assertThat(st.getTranslation(),
OrekitMatchers.vectorCloseTo(t.getTranslation(), 0));
MatcherAssert.assertThat(Rotation.distance(st.getRotation(), t.getRotation()),
OrekitMatchers.closeTo(0, 0));
}
}
}
});
}
private Vector3D computeOffsetLinearly(final double t1, final double t2, final double t3,
......
......@@ -157,20 +157,25 @@ public class ITRFVersionTest {
@Test
public void testAllConverters() {
// for this test, we arbitrarily assume FramesFactory provides an ITRF 2014
Frame itrf2014 = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
// select the last supported ITRF version
ITRFVersion last = ITRFVersion.getLast();
// for this test, we arbitrarily assume FramesFactory provides an ITRF in last supported version
Frame itrfLast = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
for (final ITRFVersion origin : ITRFVersion.values()) {
for (final ITRFVersion destination : ITRFVersion.values()) {
ITRFVersion.Converter converter = ITRFVersion.getConverter(origin, destination);
Assert.assertEquals(origin, converter.getOrigin());
Assert.assertEquals(destination, converter.getDestination());
Frame originFrame = origin == ITRFVersion.ITRF_2014 ?
itrf2014 :
from2014(origin.getYear()).createTransformedITRF(itrf2014, origin.toString());
Frame destinationFrame = destination == ITRFVersion.ITRF_2014 ?
itrf2014 :
from2014(destination.getYear()).createTransformedITRF(itrf2014, destination.toString());
Frame originFrame = origin == last ?
itrfLast :
HelmertTransformation.Predefined.selectPredefined(last.getYear(), origin.getYear()).
createTransformedITRF(itrfLast, origin.toString());
Frame destinationFrame = destination == last ?
itrfLast :
HelmertTransformation.Predefined.selectPredefined(last.getYear(), destination.getYear()).
createTransformedITRF(itrfLast, destination.toString());
for (int year = 2000; year < 2007; ++year) {
AbsoluteDate date = new AbsoluteDate(year, 4, 17, 12, 0, 0, TimeScalesFactory.getTT());
......@@ -182,26 +187,26 @@ public class ITRFVersionTest {
date,
converter.getStaticTransform(date),
destinationFrame.getStaticTransformTo(originFrame, date));
if (origin == ITRFVersion.ITRF_2008 || destination == ITRFVersion.ITRF_2008) {
// if we use ITRF 2008, as internally the pivot frame is ITRF 2014
// on side of the transform is computed as f -> 2008 -> 2014, and on
// the other side as f -> 2014 and we get some inversion in between.
if (origin != last && destination != last) {
// if we use two old ITRF, as internally the pivot frame is the last one
// one side of the transform is computed as f -> itrf-1 -> itrf-last, and on
// the other side as f -> itrf-last and we get some inversion in between.
// the errors are not strictly zero (but they are very small) because
// Helmert transformations are a translation plus a rotation. If we do
// t1 -> r1 -> t2 -> r2, it is not the same as t1 -> t2 -> r1 -> r2
// which would correspond to simply add the offsets, velocities, rotations and rate,
// which is what is done in the reference documents.
// Anyway, the non-commutativity errors are well below models accuracy
Assert.assertEquals(0, looped.getTranslation().getNorm(), 6.0e-06);
Assert.assertEquals(0, looped.getVelocity().getNorm(), 2.0e-22);
Assert.assertEquals(0, looped.getRotation().getAngle(), 2.0e-12);
Assert.assertEquals(0, looped.getTranslation().getNorm(), 3.0e-06);
Assert.assertEquals(0, looped.getVelocity().getNorm(), 9.0e-23);
Assert.assertEquals(0, looped.getRotation().getAngle(), 8.0e-13);
Assert.assertEquals(0, looped.getRotationRate().getNorm(), 2.0e-32);
} else {
// if we always stay in the ITRF 2014 branch, we do the right conversions
// if we always stay in the ITRF last branch, we do the right conversions
// and errors are at numerical noise level
Assert.assertEquals(0, looped.getTranslation().getNorm(), 6.0e-17);
Assert.assertEquals(0, looped.getTranslation().getNorm(), 2.0e-17);
Assert.assertEquals(0, looped.getVelocity().getNorm(), 4.0e-26);
Assert.assertEquals(0, looped.getRotation().getAngle(), 1.0e-40);
Assert.assertEquals(0, looped.getRotation().getAngle(), 1.0e-50);
Assert.assertEquals(0, looped.getRotationRate().getNorm(), 2.0e-32);
}
MatcherAssert.assertThat(
......@@ -216,26 +221,26 @@ public class ITRFVersionTest {
new FieldTransform<>(date64,
converter.getTransform(date64),
destinationFrame.getTransformTo(originFrame, date64));
if (origin == ITRFVersion.ITRF_2008 || destination == ITRFVersion.ITRF_2008) {
// if we use ITRF 2008, as internally the pivot frame is ITRF 2014
// on side of the transform is computed as f -> 2008 -> 2014, and on
// the other side as f -> 2014 and we get some inversion in between.
if (origin != last && destination != last) {
// if we use two old ITRF, as internally the pivot frame is the last one
// one side of the transform is computed as f -> itrf-1 -> itrf-last, and on
// the other side as f -> itrf-last and we get some inversion in between.
// the errors are not strictly zero (but they are very small) because
// Helmert transformations are a translation plus a rotation. If we do
// t1 -> r1 -> t2 -> r2, it is not the same as t1 -> t2 -> r1 -> r2
// which would correspond to simply add the offsets, velocities, rotations and rate,
// which is what is done in the reference documents.
// Anyway, the non-commutativity errors are well below models accuracy
Assert.assertEquals(0, looped64.getTranslation().getNorm().getReal(), 6.0e-06);
Assert.assertEquals(0, looped64.getVelocity().getNorm().getReal(), 2.0e-22);
Assert.assertEquals(0, looped64.getRotation().getAngle().getReal(), 2.0e-12);
Assert.assertEquals(0, looped64.getTranslation().getNorm().getReal(), 3.0e-06);
Assert.assertEquals(0, looped64.getVelocity().getNorm().getReal(), 9.0e-23);
Assert.assertEquals(0, looped64.getRotation().getAngle().getReal(), 8.0e-13);
Assert.assertEquals(0, looped64.getRotationRate().getNorm().getReal(), 2.0e-32);
} else {
// if we always stay in the ITRF 2014 branch, we do the right conversions
// if we always stay in the ITRF last branch, we do the right conversions
// and errors are at numerical noise level
Assert.assertEquals(0, looped64.getTranslation().getNorm().getReal(), 6.0e-17);
Assert.assertEquals(0, looped64.getTranslation().getNorm().getReal(), 2.0e-17);
Assert.assertEquals(0, looped64.getVelocity().getNorm().getReal(), 4.0e-26);
Assert.assertEquals(0, looped64.getRotation().getAngle().getReal(), 1.0e-40);
Assert.assertEquals(0, looped64.getRotation().getAngle().getReal(), 1.0e-50);
Assert.assertEquals(0, looped64.getRotationRate().getNorm().getReal(), 2.0e-32);
}
}
......@@ -243,24 +248,6 @@ public class ITRFVersionTest {
}
}
private HelmertTransformation.Predefined from2014(final int year) {
switch (year) {
case 1988 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1988;
case 1989 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1989;
case 1990 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1990;
case 1991 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1991;
case 1992 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1992;
case 1993 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1993;
case 1994 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1994;
case 1996 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1996;
case 1997 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_1997;
case 2000 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_2000;
case 2005 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_2005;
case 2008 : return HelmertTransformation.Predefined.ITRF_2014_TO_ITRF_2008;
default : return null;
}
}
@Before
public void setUp() {
Utils.setDataRoot("compressed-data");
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment