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.bodies;
18  
19  import org.hipparchus.geometry.euclidean.threed.Rotation;
20  import org.hipparchus.geometry.euclidean.threed.Vector3D;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.Test;
23  import org.orekit.Utils;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.frames.Frame;
26  import org.orekit.frames.FramesFactory;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.time.TimeScalesFactory;
29  import org.orekit.utils.Constants;
30  import org.orekit.utils.IERSConventions;
31  import org.orekit.utils.TimeStampedPVCoordinates;
32  
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.List;
36  import java.util.concurrent.ExecutorService;
37  import java.util.concurrent.Executors;
38  import java.util.concurrent.Future;
39  import java.util.concurrent.TimeUnit;
40  import java.util.concurrent.atomic.AtomicReference;
41  
42  public class CelestialBodyFactoryTest {
43  
44      @Test
45      public void getSun() {
46          Utils.setDataRoot("regular-data");
47  
48          CelestialBody sun = CelestialBodyFactory.getSun();
49          Assertions.assertNotNull(sun);
50      }
51  
52      @Test
53      public void clearCache() {
54          Utils.setDataRoot("regular-data");
55  
56          CelestialBody sun = CelestialBodyFactory.getSun();
57          Assertions.assertNotNull(sun);
58          CelestialBodyFactory.clearCelestialBodyCache();
59          CelestialBody sun2 = CelestialBodyFactory.getSun();
60          Assertions.assertNotNull(sun2);
61          Assertions.assertNotSame(sun, sun2);
62      }
63  
64      @Test
65      public void clearLoaders() {
66          Utils.setDataRoot("regular-data");
67  
68          CelestialBody sun = CelestialBodyFactory.getSun();
69          Assertions.assertNotNull(sun);
70          CelestialBodyFactory.clearCelestialBodyLoaders();
71          CelestialBody sun2 = CelestialBodyFactory.getSun();
72          Assertions.assertNotNull(sun2);
73          Assertions.assertNotSame(sun, sun2);
74          CelestialBodyFactory.clearCelestialBodyLoaders(CelestialBodyFactory.SUN);
75          CelestialBodyFactory.clearCelestialBodyCache(CelestialBodyFactory.SUN);
76          CelestialBodyFactory.addDefaultCelestialBodyLoader(JPLEphemeridesLoader.DEFAULT_DE_SUPPORTED_NAMES);
77          CelestialBody sun3 = CelestialBodyFactory.getSun();
78          Assertions.assertNotNull(sun3);
79          Assertions.assertNotSame(sun,  sun3);
80          Assertions.assertNotSame(sun2, sun3);
81      }
82  
83      @Test
84      void testHorizon() {
85  
86          // The following data are an excerpt from a telnet session with JPL Horizon system
87          // note that in Horizon we selected Jupiter barycenter rather than Jupiter body center
88          // this seems to match better the content of the DE-431 ephemeris
89  
90          //  *******************************************************************************
91          //  Ephemeris / PORT_LOGIN Mon Oct 26 04:53:43 2015 Pasadena, USA    / Horizons
92          //  *******************************************************************************
93          //  Target body name: Jupiter Barycenter (5)          {source: DE-0431LE-0431}
94          //  Center body name: Solar System Barycenter (0)     {source: DE-0431LE-0431}
95          //  Center-site name: BODY CENTER
96          //  *******************************************************************************
97          //  Start time      : A.D. 2000-Jan-01 00:00:00.0000 CT
98          //  Stop  time      : A.D. 2003-Dec-31 23:59:00.0000 CT
99          //  Step-size       : 1440 minutes
100         //  *******************************************************************************
101         //  Center geodetic : 0.00000000,0.00000000,0.0000000 {E-lon(deg),Lat(deg),Alt(km)}
102         //  Center cylindric: 0.00000000,0.00000000,0.0000000 {E-lon(deg),Dxy(km),Dz(km)}
103         //  Center radii    : (undefined)
104         //  Output units    : KM-S
105         //  Output format   : 02
106         //  Reference frame : ICRF/J2000.0
107         //  Output type     : GEOMETRIC cartesian states
108         //  Coordinate systm: Earth Mean Equator and Equinox of Reference Epoch
109         //  *******************************************************************************
110         //  JDCT
111         //     X     Y     Z
112         //     VX    VY    VZ
113         //  *******************************************************************************
114         //  $$SOE
115         //  2451544.500000000 = A.D. 2000-Jan-01 00:00:00.0000 (TDB)
116         //   X = 5.978411018921824E+08 Y = 4.085508359611598E+08 Z = 1.605595308103096E+08
117         //   VX=-7.892151874487445E+00 VY= 1.017751699703826E+01 VZ= 4.554715748011852E+00
118         //  2451545.500000000 = A.D. 2000-Jan-02 00:00:00.0000 (TDB)
119         //   X = 5.971584965869523E+08 Y = 4.094296790808872E+08 Z = 1.609528639632485E+08
120         //   VX=-7.908893450088906E+00 VY= 1.016606978596496E+01 VZ= 4.550216570971850E+00
121         //  2451546.500000000 = A.D. 2000-Jan-03 00:00:00.0000 (TDB)
122         //   X = 5.964744456934582E+08 Y = 4.103075321378759E+08 Z = 1.613458079269412E+08
123         //   VX=-7.925614558638352E+00 VY= 1.015459888397081E+01 VZ= 4.545706740033853E+00
124         //  2451547.500000000 = A.D. 2000-Jan-04 00:00:00.0000 (TDB)
125         //   X = 5.957889509819047E+08 Y = 4.111843930867567E+08 Z = 1.617383617815004E+08
126         //   VX=-7.942315157290042E+00 VY= 1.014310432640078E+01 VZ= 4.541186269306714E+00
127         //  2451548.500000000 = A.D. 2000-Jan-05 00:00:00.0000 (TDB)
128         //   X = 5.951020142261952E+08 Y = 4.120602598852173E+08 Z = 1.621305246082588E+08
129         //   VX=-7.958995203281466E+00 VY= 1.013158614867071E+01 VZ= 4.536655172931710E+00
130         //  2451549.500000000 = A.D. 2000-Jan-06 00:00:00.0000 (TDB)
131         //   X = 5.944136372039236E+08 Y = 4.129351304940084E+08 Z = 1.625222954897725E+08
132         //   VX=-7.975654653933506E+00 VY= 1.012004438626713E+01 VZ= 4.532113465082445E+00
133         final TimeStampedPVCoordinates[] refPV = new TimeStampedPVCoordinates[] {
134             createPV(2000, 1, 1,
135                      5.978411018921824E+08, 4.085508359611598E+08, 1.605595308103096E+08,
136                      -7.892151874487445E+00, 1.017751699703826E+01, 4.554715748011852E+00),
137             createPV(2000, 1, 2,
138                      5.971584965869523E+08, 4.094296790808872E+08, 1.609528639632485E+08,
139              -7.908893450088906E+00, 1.016606978596496E+01, 4.550216570971850E+00),
140             createPV(2000, 1, 3,
141                      5.964744456934582E+08, 4.103075321378759E+08, 1.613458079269412E+08,
142              -7.925614558638352E+00, 1.015459888397081E+01, 4.545706740033853E+00),
143             createPV(2000, 1, 4,
144                      5.957889509819047E+08, 4.111843930867567E+08, 1.617383617815004E+08,
145                      -7.942315157290042E+00, 1.014310432640078E+01, 4.541186269306714E+00),
146             createPV(2000, 1, 5,
147                      5.951020142261952E+08, 4.120602598852173E+08, 1.621305246082588E+08,
148                      -7.958995203281466E+00, 1.013158614867071E+01, 4.536655172931710E+00),
149             createPV(2000, 1, 6,
150                      5.944136372039236E+08, 4.129351304940084E+08, 1.625222954897725E+08,
151                      -7.975654653933506E+00, 1.012004438626713E+01, 4.532113465082445E+00)
152         };
153 
154         Utils.setDataRoot("regular-data");
155         final CelestialBody jupiter = CelestialBodyFactory.getJupiter();
156         final Frame icrf = FramesFactory.getICRF();
157         for (final TimeStampedPVCoordinates ref : refPV) {
158             TimeStampedPVCoordinates testPV = jupiter.getPVCoordinates(ref.getDate(),
159                     icrf);
160             Assertions.assertEquals(0.0,
161                                 Vector3D.distance(ref.getPosition(), testPV.getPosition()),
162                                 4.0e-4);
163             Assertions.assertEquals(0.0,
164                                 Vector3D.distance(ref.getVelocity(), testPV.getVelocity()),
165                                 1.0e-11);
166             Vector3D testP = jupiter.getInertiallyOrientedFrame()
167                     .getStaticTransformTo(icrf, ref.getDate())
168                     .transformPosition(Vector3D.ZERO);
169             Assertions.assertEquals(
170                     0.0,
171                     Vector3D.distance(ref.getPosition(), testP),
172                     8.0e-4);
173             testP = jupiter.getBodyOrientedFrame()
174                     .getStaticTransformTo(icrf, ref.getDate())
175                     .transformPosition(Vector3D.ZERO);
176             Assertions.assertEquals(
177                     0.0,
178                     Vector3D.distance(ref.getPosition(), testP),
179                     8.0e-4);
180         }
181 
182     }
183 
184     private TimeStampedPVCoordinates createPV(int year, int month, int day,
185                                               double xKm, double yKm, double zKM,
186                                               double vxKmS, double vyKms, double vzKms) {
187         return new TimeStampedPVCoordinates(new AbsoluteDate(year, month, day, TimeScalesFactory.getTDB()),
188                                             new Vector3D(  xKm * 1000,   yKm * 1000,   zKM * 1000),
189                                             new Vector3D(vxKmS * 1000, vyKms * 1000, vzKms * 1000));
190     }
191 
192     @Test
193     public void multithreadTest() {
194         Utils.setDataRoot("regular-data");
195         checkMultiThread(10, 100);
196     }
197 
198     private void checkMultiThread(final int threads, final int runs) {
199 
200         final AtomicReference<OrekitException> caught = new AtomicReference<OrekitException>();
201         ExecutorService executorService = Executors.newFixedThreadPool(threads);
202 
203         List<Future<?>> results = new ArrayList<Future<?>>();
204         for (int i = 0; i < threads; i++) {
205             Future<?> result = executorService.submit(new Runnable() {
206                 public void run() {
207                     try {
208                         for (int run = 0; run < runs; run++) {
209                             CelestialBody mars = CelestialBodyFactory.getBody(CelestialBodyFactory.MARS);
210                             Assertions.assertNotNull(mars);
211                             CelestialBodyFactory.clearCelestialBodyLoaders();
212                         }
213                     } catch (OrekitException oe) {
214                         caught.set(oe);
215                     }
216                 }
217             });
218             results.add(result);
219         }
220 
221         try {
222             executorService.shutdown();
223             executorService.awaitTermination(5, TimeUnit.SECONDS);
224         } catch (InterruptedException ie) {
225             Assertions.fail(ie.getLocalizedMessage());
226         }
227 
228         for (Future<?> result : results) {
229             Assertions.assertTrue(result.isDone(),"Not all threads finished -> possible deadlock");
230         }
231 
232         if (caught.get() != null) {
233             throw caught.get();
234         }
235     }
236 
237     @Test
238     void testEarthMoonBarycenter() {
239         Utils.setDataRoot("regular-data/de405-ephemerides");
240         CelestialBody sun = CelestialBodyFactory.getSun();
241         CelestialBody mars = CelestialBodyFactory.getMars();
242         CelestialBody earth = CelestialBodyFactory.getEarth();
243         CelestialBody earthMoonBarycenter = CelestialBodyFactory.getEarthMoonBarycenter();
244         List<Frame> frames = Arrays.asList(FramesFactory.getEME2000(),
245                                            FramesFactory.getGCRF(),
246                                            sun.getInertiallyOrientedFrame(),
247                                            mars.getInertiallyOrientedFrame(),
248                                            earth.getInertiallyOrientedFrame());
249 
250         AbsoluteDate date = new AbsoluteDate(1969, 7, 23, TimeScalesFactory.getTT());
251         final double refDistance = bodyDistance(sun, earthMoonBarycenter, date, frames.get(0));
252         for (Frame frame : frames) {
253             Assertions.assertEquals(refDistance,
254                                 bodyDistance(sun, earthMoonBarycenter, date, frame),
255                                 1.0e-14 * refDistance,frame.toString());
256         }
257     }
258 
259     @Test
260     void testICRFAndGCRFAlignment() {
261         Utils.setDataRoot("regular-data");
262         final CelestialBody earthMoonBarycenter   = CelestialBodyFactory.getEarthMoonBarycenter();
263         final CelestialBody solarSystemBarycenter = CelestialBodyFactory.getSolarSystemBarycenter();
264         final List<Frame> frames = Arrays.asList(earthMoonBarycenter.getInertiallyOrientedFrame(),
265                                                  earthMoonBarycenter.getBodyOrientedFrame(),
266                                                  solarSystemBarycenter.getInertiallyOrientedFrame(),
267                                                  solarSystemBarycenter.getBodyOrientedFrame());
268         final Frame icrf = FramesFactory.getICRF();
269         final Frame gcrf = FramesFactory.getGCRF();
270         for (double dt = 0; dt < Constants.JULIAN_DAY; dt += 60) {
271             final AbsoluteDate date = AbsoluteDate.J2000_EPOCH.shiftedBy(dt);
272             for (final Frame frame : frames) {
273                 Assertions.assertEquals(0.0, frame.getTransformTo(icrf, date).getRotation().getAngle(), 1.0e-15);
274                 Assertions.assertEquals(0.0, frame.getTransformTo(gcrf, date).getRotation().getAngle(), 1.0e-15);
275                 Assertions.assertEquals(0.0, frame.getStaticTransformTo(icrf, date).getRotation().getAngle(), 1.0e-15);
276                 Assertions.assertEquals(0.0, frame.getStaticTransformTo(gcrf, date).getRotation().getAngle(), 1.0e-15);
277             }
278         }
279     }
280 
281     @Test
282     void testEarthInertialFrameAroundJ2000() {
283         Utils.setDataRoot("regular-data");
284         final Frame earthFrame = CelestialBodyFactory.getEarth().getInertiallyOrientedFrame();
285         final Frame base       = FramesFactory.getGCRF();
286         final Rotation reference = new Rotation(Vector3D.PLUS_K, Vector3D.PLUS_J,
287                                                 Vector3D.PLUS_K, Vector3D.PLUS_I);
288          for (double dt = -60; dt <= 60; dt += 1.0) {
289              final AbsoluteDate date = AbsoluteDate.J2000_EPOCH.shiftedBy(dt);
290              Rotation rotation = base.getTransformTo(earthFrame, date).getRotation();
291              Assertions.assertEquals(0.0, Rotation.distance(reference, rotation), 3.0e-10);
292          }
293     }
294 
295     @Test
296     void testEarthBodyOrientedFrameAroundJ2000() {
297         Utils.setDataRoot("regular-data");
298         final Frame earthFrame = CelestialBodyFactory.getEarth().getBodyOrientedFrame();
299         final Frame base       = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
300          for (double dt = -60; dt <= 60; dt += 1.0) {
301              final AbsoluteDate date = AbsoluteDate.J2000_EPOCH.shiftedBy(dt);
302              Rotation rotation = base.getTransformTo(earthFrame, date).getRotation();
303              Assertions.assertEquals(7.9426e-4, Rotation.distance(Rotation.IDENTITY, rotation), 1.0e-8);
304          }
305     }
306 
307     private double bodyDistance(CelestialBody body1, CelestialBody body2, AbsoluteDate date, Frame frame)
308         {
309         Vector3D body1Position = body1.getPosition(date, frame);
310         Vector3D body2Position = body2.getPosition(date, frame);
311         Vector3D bodyPositionDifference = body1Position.subtract(body2Position);
312         return bodyPositionDifference.getNorm();
313     }
314 
315 }