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  
18  package org.orekit.models.earth.atmosphere.data;
19  
20  import org.hipparchus.ode.ODEIntegrator;
21  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
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.Utils;
27  import org.orekit.bodies.CelestialBody;
28  import org.orekit.bodies.CelestialBodyFactory;
29  import org.orekit.bodies.OneAxisEllipsoid;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.forces.drag.DragForce;
33  import org.orekit.forces.drag.IsotropicDrag;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.FramesFactory;
36  import org.orekit.models.earth.atmosphere.Atmosphere;
37  import org.orekit.models.earth.atmosphere.JB2008;
38  import org.orekit.models.earth.atmosphere.JB2008InputParameters;
39  import org.orekit.orbits.KeplerianOrbit;
40  import org.orekit.orbits.Orbit;
41  import org.orekit.orbits.OrbitType;
42  import org.orekit.orbits.PositionAngleType;
43  import org.orekit.propagation.SpacecraftState;
44  import org.orekit.propagation.numerical.NumericalPropagator;
45  import org.orekit.propagation.sampling.OrekitStepHandler;
46  import org.orekit.time.AbsoluteDate;
47  import org.orekit.time.TimeScale;
48  import org.orekit.time.TimeScalesFactory;
49  import org.orekit.utils.Constants;
50  import org.orekit.utils.IERSConventions;
51  
52  import static org.hamcrest.MatcherAssert.assertThat;
53  import static org.orekit.OrekitMatchers.closeTo;
54  import static org.orekit.OrekitMatchers.pvCloseTo;
55  
56  
57  /*
58   * Test code based on the CssiSpaceWeatherDataTest class
59   * by Clément Jonglez.
60   * @author Louis Aucouturier
61   * @since 11.2
62   */
63  
64  public class DtcDataLoaderTest {
65  
66      private TimeScale utc;
67  
68      @BeforeEach
69      public void setUp() {
70          Utils.setDataRoot("regular-data:atmosphere");
71          utc = TimeScalesFactory.getUTC();
72      }
73  
74      // DataLoader
75      private JB2008SpaceEnvironmentData loadJB() {
76          return loadJB("DTCFILE_trunc.TXT");
77      }
78  
79      // DataLoader with DTCFILE filename to be defined
80      private JB2008SpaceEnvironmentData loadJB(final String filename) {
81          JB2008SpaceEnvironmentData JBData = new JB2008SpaceEnvironmentData("SOLFSMY_trunc.txt", filename);
82          return JBData;
83      }
84  
85  
86  
87      @Test
88      public void testNoDataException() {
89          try {
90              loadJB("DTCFILE_empty.TXT");
91              Assertions.fail("No Data In File exception should have been raised");
92          } catch (OrekitException oe) {
93              Assertions.assertEquals(OrekitMessages.NO_DATA_IN_FILE, oe.getSpecifier());
94          }
95      }
96  
97      @Test
98      public void testUnableParse() {
99          try {
100             loadJB("DTCFILE_badparse.TXT");
101             Assertions.fail("UNABLE_TO_PARSE_LINE_IN_FILE exception should have been raised");
102         } catch (OrekitException oe) {
103             Assertions.assertEquals(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE, oe.getSpecifier());
104         }
105     }
106 
107     @Test
108     public void testParseDouble() {
109         JB2008SpaceEnvironmentData JBData = loadJB("DTCFILE_double.txt");
110         final AbsoluteDate date = new AbsoluteDate(2004, 1, 1, 0, 0, 0.0, utc);
111         assertThat(135.0, closeTo(JBData.getDSTDTC(date), 1e-10));
112     }
113 
114 
115     @Test
116     public void testMinDate() {
117         JB2008SpaceEnvironmentData JBData = loadJB();
118         final AbsoluteDate startDate = new AbsoluteDate(2003, 12, 26, 12, 0, 0.0, utc);
119         Assertions.assertEquals(startDate, JBData.getMinDate());
120     }
121 
122 
123 
124     @Test
125     public void testMaxDate() {
126         JB2008SpaceEnvironmentData JBData = loadJB();
127         final AbsoluteDate lastDate = new AbsoluteDate(2007, 1, 1, 12, 0, 0.0, utc);
128         Assertions.assertEquals(lastDate, JBData.getMaxDate());
129     }
130 
131     @Test
132     public void testDTC() {
133         JB2008SpaceEnvironmentData JBData = loadJB();
134         final AbsoluteDate date = new AbsoluteDate(2004, 1, 1, 0, 0, 0.0, utc);
135         assertThat(135.0, closeTo(JBData.getDSTDTC(date), 1e-10));
136     }
137 
138     @Test
139     public void testDTCInterp() {
140         JB2008SpaceEnvironmentData JBData = loadJB();
141         final AbsoluteDate date = new AbsoluteDate(2004, 1, 3, 0, 30, 0.0, utc);
142         assertThat((85.0 + 94.0)/2, closeTo(JBData.getDSTDTC(date), 1e-10));
143     }
144 
145 
146     @Test
147     public void testBracketDateDTC_lastDate() {
148         JB2008SpaceEnvironmentData JBData = loadJB();
149         AbsoluteDate date = new AbsoluteDate(2050, 10, 1, 5, 17, 0.0, utc);
150         try {
151             JBData.getDSTDTC(date);
152             Assertions.fail("an exception should have been thrown");
153         } catch (OrekitException oe) {
154             Assertions.assertEquals(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_AFTER, oe.getSpecifier());
155         }
156     }
157 
158     @Test
159     public void testBracketDateDTC_firstDate() {
160         JB2008SpaceEnvironmentData JBData = loadJB();
161         AbsoluteDate date = new AbsoluteDate(1957, 10, 1, 5, 17, 0.0, utc);
162         try {
163             JBData.getDSTDTC(date);
164             Assertions.fail("an exception should have been thrown");
165         } catch (OrekitException oe) {
166             Assertions.assertEquals(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE, oe.getSpecifier());
167         }
168     }
169 
170     /**
171      * Check integration error is small when integrating the same equations over the same
172      * interval.
173      */
174     @Test
175     public void testWithPropagator() {
176         CelestialBody sun = CelestialBodyFactory.getSun();
177         final Frame eci = FramesFactory.getGCRF();
178         final Frame ecef = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
179         AbsoluteDate date = new AbsoluteDate(2004, 1, 2, utc);
180         OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
181                 Constants.WGS84_EARTH_FLATTENING, ecef);
182         Orbit orbit = new KeplerianOrbit(6378137 + 400e3, 1e-3, FastMath.toRadians(50), 0, 0, 0, PositionAngleType.TRUE,
183                 eci, date, Constants.EIGEN5C_EARTH_MU);
184         final SpacecraftState ic = new SpacecraftState(orbit);
185 
186         final AbsoluteDate end = date.shiftedBy(5 * Constants.JULIAN_DAY);
187         final AbsoluteDate resetDate = date.shiftedBy(0.8 * Constants.JULIAN_DAY + 0.1);
188 
189         final SpacecraftState[] lastState = new SpacecraftState[1];
190         final OrekitStepHandler stepSaver = interpolator -> {
191             final AbsoluteDate start = interpolator.getPreviousState().getDate();
192             if (start.compareTo(resetDate) < 0) {
193                 lastState[0] = interpolator.getPreviousState();
194             }
195         };
196 
197         // propagate with state rest to take slightly different path
198         NumericalPropagator propagator = getNumericalPropagatorWithJB2008(sun, earth, ic);
199         propagator.setStepHandler(stepSaver);
200         propagator.propagate(resetDate);
201         propagator.resetInitialState(lastState[0]);
202         propagator.clearStepHandlers();
203         SpacecraftState actual = propagator.propagate(end);
204 
205         // propagate straight through
206         propagator = getNumericalPropagatorWithJB2008(sun, earth, ic);
207         propagator.resetInitialState(ic);
208         propagator.clearStepHandlers();
209         SpacecraftState expected = propagator.propagate(end);
210 
211         assertThat(actual.getPVCoordinates(), pvCloseTo(expected.getPVCoordinates(), 1.0));
212 
213     }
214 
215     /**
216      * Configure a numerical propagator with DTM2000 atmosphere.
217      *
218      * @param sun   Sun.
219      * @param earth Earth.
220      * @param ic    initial condition.
221      * @return a propagator with DTM2000 atmosphere.
222      */
223     private NumericalPropagator getNumericalPropagatorWithJB2008(CelestialBody sun, OneAxisEllipsoid earth,
224             SpacecraftState ic) {
225         // some non-integer step size to induce truncation error in flux interpolation
226         final ODEIntegrator integrator = new ClassicalRungeKuttaIntegrator(120 + 0.1);
227         NumericalPropagator propagator = new NumericalPropagator(integrator);
228         JB2008InputParameters JBData = loadJB();
229         final Atmosphere atmosphere = new JB2008(JBData, sun, earth);
230         final IsotropicDrag satellite = new IsotropicDrag(1, 3.2);
231         propagator.addForceModel(new DragForce(atmosphere, satellite));
232 
233         propagator.setInitialState(ic);
234         propagator.setOrbitType(OrbitType.CARTESIAN);
235 
236         return propagator;
237     }
238 }
239