1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.frames;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20
21 import java.time.Instant;
22 import java.time.LocalDateTime;
23 import java.time.ZoneId;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 import org.hipparchus.Field;
28 import org.hipparchus.geometry.euclidean.threed.FieldRotation;
29 import org.hipparchus.geometry.euclidean.threed.Rotation;
30 import org.hipparchus.geometry.euclidean.threed.RotationConvention;
31 import org.hipparchus.geometry.euclidean.threed.RotationOrder;
32 import org.hipparchus.geometry.euclidean.threed.Vector3D;
33 import org.hipparchus.util.Binary64;
34 import org.hipparchus.util.Binary64Field;
35 import org.hipparchus.util.FastMath;
36 import org.hipparchus.util.Pair;
37 import org.junit.jupiter.api.Assertions;
38 import org.junit.jupiter.api.BeforeEach;
39 import org.junit.jupiter.api.Test;
40 import org.mockito.Mockito;
41 import org.orekit.Utils;
42 import org.orekit.attitudes.Attitude;
43 import org.orekit.attitudes.AttitudeProvider;
44 import org.orekit.attitudes.LofOffset;
45 import org.orekit.models.earth.GeoMagneticField;
46 import org.orekit.models.earth.GeoMagneticFieldFactory;
47 import org.orekit.orbits.KeplerianOrbit;
48 import org.orekit.orbits.Orbit;
49 import org.orekit.orbits.PositionAngleType;
50 import org.orekit.propagation.SpacecraftState;
51 import org.orekit.time.AbsoluteDate;
52 import org.orekit.time.DateComponents;
53 import org.orekit.time.FieldAbsoluteDate;
54 import org.orekit.time.TimeComponents;
55 import org.orekit.time.TimeScalesFactory;
56 import org.orekit.utils.Constants;
57 import org.orekit.utils.FieldPVCoordinates;
58 import org.orekit.utils.IERSConventions;
59 import org.orekit.utils.PVCoordinates;
60
61 public class LocalMagneticFieldFrameTest {
62
63 @BeforeEach
64 public void setUp() {
65 Utils.setDataRoot("regular-data:earth");
66 }
67
68 @Test
69 void testDefaultConstructorAndName() {
70
71 final Frame inertialFrameMock = Mockito.mock(Frame.class);
72 final Frame bodyFrameMock = Mockito.mock(Frame.class);
73 final GeoMagneticField geoMagneticFieldMock = Mockito.mock(GeoMagneticField.class);
74
75 final LocalMagneticFieldFrame localMagneticFieldFrame =
76 new LocalMagneticFieldFrame(inertialFrameMock, geoMagneticFieldMock, bodyFrameMock);
77
78
79 final String computedName = localMagneticFieldFrame.getName();
80
81
82
83 Assertions.assertEquals("LOCAL_MAGNETIC_FIELD_FRAME", computedName);
84
85
86 Assertions.assertEquals(inertialFrameMock, localMagneticFieldFrame.getInertialFrame());
87 Assertions.assertEquals(geoMagneticFieldMock, localMagneticFieldFrame.getMagneticField());
88 }
89
90
91 @Test
92 void testFieldRotation() {
93
94
95 final Frame inertialFrameMock = Mockito.mock(Frame.class);
96 final Frame bodyFrameMock = Mockito.mock(Frame.class);
97 final GeoMagneticField geoMagneticFieldMock = Mockito.mock(GeoMagneticField.class);
98
99 final LocalMagneticFieldFrame localMagneticFieldFrame =
100 new LocalMagneticFieldFrame(inertialFrameMock, geoMagneticFieldMock, bodyFrameMock);
101
102
103 final LocalMagneticFieldFrame spy = Mockito.spy(localMagneticFieldFrame);
104
105
106 final double q0 = 0;
107 final double q1 = 0.707;
108 final double q2 = 0;
109 final double q3 = 0.707;
110 final Rotation rotation = new Rotation(q0, q1, q2, q3, true);
111 Mockito.doReturn(rotation).when(spy).rotationFromInertial(Mockito.any(AbsoluteDate.class),
112 Mockito.any(PVCoordinates.class));
113
114
115 final AbsoluteDate date = Mockito.mock(AbsoluteDate.class);
116 @SuppressWarnings("unchecked")
117 final FieldAbsoluteDate<Binary64> fieldDate = Mockito.mock(FieldAbsoluteDate.class);
118 Mockito.when(fieldDate.toAbsoluteDate()).thenReturn(date);
119
120
121 final PVCoordinates pv = Mockito.mock(PVCoordinates.class);
122 @SuppressWarnings("unchecked")
123 final FieldPVCoordinates<Binary64> fieldPV = Mockito.mock(FieldPVCoordinates.class);
124 Mockito.when(fieldPV.toPVCoordinates()).thenReturn(pv);
125
126
127 final Field<Binary64> binary64Field = Binary64Field.getInstance();
128
129
130 final FieldRotation<Binary64> computedRotation = spy.rotationFromInertial(binary64Field, fieldDate, fieldPV);
131
132
133 Assertions.assertEquals(rotation.getQ0(), computedRotation.getQ0().getReal());
134 Assertions.assertEquals(rotation.getQ1(), computedRotation.getQ1().getReal());
135 Assertions.assertEquals(rotation.getQ2(), computedRotation.getQ2().getReal());
136 Assertions.assertEquals(rotation.getQ3(), computedRotation.getQ3().getReal());
137 }
138
139 @Test
140 void testAligningJDirection() {
141
142
143 final Vector3D positionMock = Mockito.mock(Vector3D.class);
144 final Vector3D velocityMock = Mockito.mock(Vector3D.class);
145 final Vector3D momentumMock = Mockito.mock(Vector3D.class);
146 final Vector3D minusPositionMock = Mockito.mock(Vector3D.class);
147 final Vector3D minusVelocityMock = Mockito.mock(Vector3D.class);
148 final Vector3D minusMomentumMock = Mockito.mock(Vector3D.class);
149
150 Mockito.when(positionMock.negate()).thenReturn(minusPositionMock);
151 Mockito.when(velocityMock.negate()).thenReturn(minusVelocityMock);
152 Mockito.when(momentumMock.negate()).thenReturn(minusMomentumMock);
153
154 Mockito.when(Vector3D.crossProduct(positionMock, velocityMock)).thenReturn(momentumMock);
155
156 final PVCoordinates pv = new PVCoordinates(positionMock, velocityMock);
157
158
159 final LocalMagneticFieldFrame.LOFBuilderVector plusPos = LocalMagneticFieldFrame.LOFBuilderVector.PLUS_POSITION;
160 final LocalMagneticFieldFrame.LOFBuilderVector plusVel = LocalMagneticFieldFrame.LOFBuilderVector.PLUS_VELOCITY;
161 final LocalMagneticFieldFrame.LOFBuilderVector plusMom = LocalMagneticFieldFrame.LOFBuilderVector.PLUS_MOMENTUM;
162 final LocalMagneticFieldFrame.LOFBuilderVector minusPos =
163 LocalMagneticFieldFrame.LOFBuilderVector.MINUS_POSITION;
164 final LocalMagneticFieldFrame.LOFBuilderVector minusVel =
165 LocalMagneticFieldFrame.LOFBuilderVector.MINUS_VELOCITY;
166 final LocalMagneticFieldFrame.LOFBuilderVector minusMom =
167 LocalMagneticFieldFrame.LOFBuilderVector.MINUS_MOMENTUM;
168
169
170 final Vector3D computedPlusPos = plusPos.getVector(pv);
171 final Vector3D computedPlusVel = plusVel.getVector(pv);
172 final Vector3D computedPlusMom = plusMom.getVector(pv);
173 final Vector3D computedMinusPos = minusPos.getVector(pv);
174 final Vector3D computedMinusVel = minusVel.getVector(pv);
175 final Vector3D computedMinusMom = minusMom.getVector(pv);
176
177
178 Assertions.assertEquals(positionMock, computedPlusPos);
179 Assertions.assertEquals(velocityMock, computedPlusVel);
180 Assertions.assertEquals(momentumMock, computedPlusMom);
181 Assertions.assertEquals(minusPositionMock, computedMinusPos);
182 Assertions.assertEquals(minusVelocityMock, computedMinusVel);
183 Assertions.assertEquals(minusMomentumMock, computedMinusMom);
184 }
185
186 @Test
187 public void testBotPointing() {
188
189 final Map<String, Pair<Rotation, Double[]>> rotationAnglesMap = new HashMap<>();
190 rotationAnglesMap.put("+x", new Pair<>(new Rotation(1.0, 0.0, 0.0, 0.0, false),
191 new Double[] { FastMath.toRadians(0.0), FastMath.toRadians(0.0) }));
192 rotationAnglesMap.put("+y", new Pair<>(new Rotation(0.7071068, 0.0, 0.0, 0.7071068, false),
193 new Double[] { FastMath.toRadians(90.0), FastMath.toRadians(0.0) }));
194 rotationAnglesMap.put("+z", new Pair<>(new Rotation(Vector3D.PLUS_K, Vector3D.PLUS_I),
195 new Double[] { FastMath.toRadians(90.0), FastMath.toRadians(90.0) }));
196 rotationAnglesMap.put("-x", new Pair<>(new Rotation(0.0, 0.0, 1.0, 0.0, false),
197 new Double[] { FastMath.toRadians(180.0), FastMath.toRadians(0.0) }));
198 rotationAnglesMap.put("-y", new Pair<>(new Rotation(Vector3D.MINUS_J, Vector3D.PLUS_I),
199 new Double[] { FastMath.toRadians(90.0), FastMath.toRadians(180.0) }));
200 rotationAnglesMap.put("-z", new Pair<>(new Rotation(Vector3D.MINUS_K, Vector3D.PLUS_I),
201 new Double[] { FastMath.toRadians(90.0), FastMath.toRadians(-90.0) }));
202
203
204 final ZoneId utcTime = ZoneId.of("UTC");
205
206
207 final Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, false);
208
209
210 final LocalDateTime utcDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(1532785820000L), utcTime);
211 final GeoMagneticField model = GeoMagneticFieldFactory.getIGRF(utcDate.getYear());
212
213 final Frame frame = FramesFactory.getEME2000();
214 final AbsoluteDate date = new AbsoluteDate(new DateComponents(2018, 7, 28),
215 new TimeComponents(13, 50, 20.00),
216 TimeScalesFactory.getTAI());
217
218 for (Map.Entry<String, Pair<Rotation, Double[]>> entry : rotationAnglesMap.entrySet()) {
219 String axis = entry.getKey();
220 Rotation rotate = entry.getValue().getFirst();
221 Double[] expectedThetaPhi = entry.getValue().getSecond();
222
223
224 final double[] rotationAngles = rotate.getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR);
225 final LOF lof =
226 new LocalMagneticFieldFrame(frame, model, LocalMagneticFieldFrame.LOFBuilderVector.PLUS_MOMENTUM,
227 itrf);
228
229 final AttitudeProvider bdotPointing = new LofOffset(frame, lof, RotationOrder.XYZ,
230 rotationAngles[0], rotationAngles[1], rotationAngles[2]);
231
232
233 KeplerianOrbit orbit = new KeplerianOrbit(6866719.5237, 0.003, FastMath.toRadians(89.7),
234 0, 0, 0, PositionAngleType.TRUE, frame, date, 3.986004415e14);
235 Attitude equatorialAttitude = bdotPointing.getAttitude(orbit, date, frame);
236 SpacecraftState spacecraftStateEquatorial = new SpacecraftState(orbit, equatorialAttitude);
237
238
239 double h = orbit.getKeplerianPeriod() / 4.0;
240
241
242 Orbit southPoleOrbit = orbit.shiftedBy(h);
243 AbsoluteDate southPoleDate = southPoleOrbit.getDate();
244 Attitude southPoleAttitude = bdotPointing.getAttitude(southPoleOrbit, southPoleDate, frame);
245 SpacecraftState spacecraftStateSouthPole = new SpacecraftState(southPoleOrbit, southPoleAttitude);
246
247
248 Orbit northPoleOrbit = orbit.shiftedBy(-h);
249 AbsoluteDate northPoleDate = northPoleOrbit.getDate();
250 Attitude northPoleAttitude = bdotPointing.getAttitude(northPoleOrbit, northPoleDate, frame);
251 SpacecraftState spacecraftStateNorthPole = new SpacecraftState(northPoleOrbit, northPoleAttitude);
252
253
254
255
256
257 checkMagneticFieldAngles(axis, expectedThetaPhi, itrf, model, spacecraftStateEquatorial);
258 checkMagneticFieldAngles(axis, expectedThetaPhi, itrf, model, spacecraftStateSouthPole);
259 checkMagneticFieldAngles(axis, expectedThetaPhi, itrf, model, spacecraftStateNorthPole);
260 }
261 }
262
263 private void checkMagneticFieldAngles(final String axis, final Double[] expectedThetaPhi, final Frame itrf,
264 final GeoMagneticField model, SpacecraftState s) {
265
266 Vector3D posItrf = s.getPosition(itrf);
267 double lat = posItrf.getDelta();
268 double lng = posItrf.getAlpha();
269 double alt = posItrf.getNorm() - Constants.WGS84_EARTH_EQUATORIAL_RADIUS;
270 Vector3D magneticFieldVector = model.calculateField(lat, lng, alt).getFieldVector();
271 Vector3D magneticFieldVectorInertial = itrf.getTransformTo(s.getFrame(), s.getDate())
272 .transformVector(magneticFieldVector);
273
274
275 double[] magneticThetaPhi = getProjectionAngles(s, magneticFieldVectorInertial);
276
277 double toll = FastMath.toRadians(1e-3);
278 assertEquals(expectedThetaPhi[0], magneticThetaPhi[0], toll,
279 axis + " Theta angle, lat:" + FastMath.toDegrees(lat) + ", lng:" + FastMath.toDegrees(lng));
280 if (FastMath.sin(expectedThetaPhi[0]) > 1e-5) {
281 assertEquals(expectedThetaPhi[1], magneticThetaPhi[1], toll,
282 axis + " Phi angle, lat:" + FastMath.toDegrees(lat) + ", lng:" + FastMath.toDegrees(lng));
283 }
284 }
285
286
287
288
289
290
291
292
293
294 private double[] getProjectionAngles(final SpacecraftState s, final Vector3D vector) {
295
296 Transform toSpacecraftBody = s.toTransform();
297 Vector3D vectorInCurrentStateFrame = toSpacecraftBody.transformVector(vector);
298 double elevation = FastMath.abs(vectorInCurrentStateFrame.getDelta()) < 1e-5 ?
299 0.0 : vectorInCurrentStateFrame.getDelta();
300 double azimuth = FastMath.abs(vectorInCurrentStateFrame.getAlpha()) < 1e-5 ?
301 0.0 : vectorInCurrentStateFrame.getAlpha();
302 double theta = Math.acos(Math.cos(elevation) * Math.cos(azimuth));
303 double phi = Math.atan2(Math.tan(elevation), Math.sin(azimuth));
304 return new double[] { theta, phi };
305 }
306
307 }
308