1   /* 
2    * Copyright 2020 Clément Jonglez
3    * Licensed to CS GROUP (CS) under one or more
4    * contributor license agreements.  See the NOTICE file distributed with
5    * this work for additional information regarding copyright ownership.
6    * Clément Jonglez licenses this file to You under the Apache License, Version 2.0
7    * (the "License"); you may not use this file except in compliance with
8    * the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.orekit.models.earth.atmosphere.data;
20  
21  import static org.hamcrest.MatcherAssert.assertThat;
22  import static org.orekit.OrekitMatchers.closeTo;
23  import static org.orekit.OrekitMatchers.pvCloseTo;
24  
25  import org.hipparchus.ode.ODEIntegrator;
26  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
27  import org.hipparchus.util.FastMath;
28  import org.junit.Assert;
29  import org.junit.Before;
30  import org.junit.Test;
31  import org.orekit.Utils;
32  import org.orekit.bodies.CelestialBody;
33  import org.orekit.bodies.CelestialBodyFactory;
34  import org.orekit.bodies.OneAxisEllipsoid;
35  import org.orekit.errors.OrekitException;
36  import org.orekit.errors.OrekitMessages;
37  import org.orekit.forces.drag.DragForce;
38  import org.orekit.forces.drag.IsotropicDrag;
39  import org.orekit.frames.Frame;
40  import org.orekit.frames.FramesFactory;
41  import org.orekit.models.earth.atmosphere.Atmosphere;
42  import org.orekit.models.earth.atmosphere.DTM2000;
43  import org.orekit.models.earth.atmosphere.DTM2000InputParameters;
44  import org.orekit.models.earth.atmosphere.NRLMSISE00;
45  import org.orekit.models.earth.atmosphere.NRLMSISE00InputParameters;
46  import org.orekit.orbits.KeplerianOrbit;
47  import org.orekit.orbits.Orbit;
48  import org.orekit.orbits.OrbitType;
49  import org.orekit.orbits.PositionAngle;
50  import org.orekit.propagation.SpacecraftState;
51  import org.orekit.propagation.numerical.NumericalPropagator;
52  import org.orekit.propagation.sampling.OrekitStepHandler;
53  import org.orekit.time.AbsoluteDate;
54  import org.orekit.time.TimeScale;
55  import org.orekit.time.TimeScalesFactory;
56  import org.orekit.utils.Constants;
57  import org.orekit.utils.IERSConventions;
58  
59  /**
60   *
61   * @author Clément Jonglez
62   */
63  public class CssiSpaceWeatherLoaderTest {
64      private TimeScale utc;
65  
66      @Before
67      public void setUp() {
68          Utils.setDataRoot("regular-data:atmosphere");
69          utc = TimeScalesFactory.getUTC();
70      }
71  
72      private CssiSpaceWeatherData loadCswl() {
73          CssiSpaceWeatherData cswl = new CssiSpaceWeatherData(CssiSpaceWeatherData.DEFAULT_SUPPORTED_NAMES);
74          return cswl;
75      }
76  
77      @Test
78      public void testMinDate() {
79          CssiSpaceWeatherData cswl = loadCswl();
80          Assert.assertEquals(new AbsoluteDate("1957-10-01", utc), cswl.getMinDate());
81      }
82  
83      @Test
84      public void testMaxDate() {
85          CssiSpaceWeatherData cswl = loadCswl();
86          Assert.assertEquals(new AbsoluteDate("2044-06-01", utc), cswl.getMaxDate());
87      }
88  
89      @Test
90      public void testThreeHourlyKpObserved() {
91          CssiSpaceWeatherData cswl = loadCswl();
92          AbsoluteDate date = new AbsoluteDate(1957, 10, 10, 0, 0, 0.0, utc);
93          final double kp = cswl.getThreeHourlyKP(date);
94          assertThat(kp, closeTo(3.0, 1e-10));
95      }
96  
97      @Test
98      /**
99       * Requests a date between two months, requiring interpolation
100      */
101     public void testThreeHourlyKpMonthlyPredicted() {
102         CssiSpaceWeatherData cswl = loadCswl();
103         AbsoluteDate date = new AbsoluteDate(2038, 6, 16, 0, 0, 0.0, utc);
104         final double kp = cswl.getThreeHourlyKP(date);
105         assertThat(kp, closeTo((2.7 + 4.1) / 2, 1e-3));
106     }
107 
108     @Test
109     /**
110      * Testing first day of data
111      * Because the Ap up to 57 hours prior to the date are asked, 
112      * this should return an exception
113      */
114     public void testThreeHourlyApObservedFirstDay() {
115         CssiSpaceWeatherData cswl = loadCswl();
116         AbsoluteDate date = new AbsoluteDate(1957, 10, 1, 5, 17, 0.0, utc);
117         try {
118             cswl.getAp(date);
119             Assert.fail("an exception should have been thrown");
120         } catch (OrekitException oe) {
121             Assert.assertEquals(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE, oe.getSpecifier());
122         }
123     }
124 
125     @Test
126     /**
127      * Testing second day of data
128      * Because the Ap up to 57 hours prior to the date are asked, 
129      * this should return an exception
130      */
131     public void testThreeHourlyApObservedSecondDay() {
132         CssiSpaceWeatherData cswl = loadCswl();
133         AbsoluteDate date = new AbsoluteDate(1957, 10, 2, 3, 14, 0.0, utc);
134         try {
135             cswl.getAp(date);
136             Assert.fail("an exception should have been thrown");
137         } catch (OrekitException oe) {
138             Assert.assertEquals(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE, oe.getSpecifier());
139         }
140     }
141 
142     @Test
143     /**
144      * Testing third day of data
145      * Because the Ap up to 57 hours prior to the date are asked, 
146      * this should return an exception
147      */
148     public void testThreeHourlyApObservedThirdDay() {
149         CssiSpaceWeatherData cswl = loadCswl();
150         AbsoluteDate date = new AbsoluteDate(1957, 10, 3, 3, 14, 0.0, utc);
151         try {
152             cswl.getAp(date);
153             Assert.fail("an exception should have been thrown");
154         } catch (OrekitException oe) {
155             Assert.assertEquals(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE, oe.getSpecifier());
156         }
157     }
158 
159     @Test
160     /**
161      * Here, no more side effects are expected
162      */
163     public void testThreeHourlyApObserved() {
164         CssiSpaceWeatherData cswl = loadCswl();
165         AbsoluteDate date = new AbsoluteDate(1957, 10, 10, 3, 14, 0.0, utc);
166         final double[] apExpected = new double[] { 18, 27, 15, 9, 7, 5.625, 3.625 };
167         final double[] ap = cswl.getAp(date);
168         for (int i = 0; i < 7; i++) {
169             assertThat(ap[i], closeTo(apExpected[i], 1e-10));
170         }
171     }
172 
173     @Test
174     /**
175      * This test is very approximate, at least to check that the two proper months were used for the interpolation 
176      * But the manual interpolation of all 7 coefficients would have been a pain
177      */
178     public void testThreeHourlyApMonthlyPredicted() {
179         CssiSpaceWeatherData cswl = loadCswl();
180         AbsoluteDate date = new AbsoluteDate(2038, 6, 16, 0, 0, 0.0, utc);
181         final double[] ap = cswl.getAp(date);
182         for (int i = 0; i < 7; i++) {
183             assertThat(ap[i], closeTo((12 + 29) / 2, 1.0));
184         }
185     }
186 
187     @Test
188     public void testDailyFlux() {
189         CssiSpaceWeatherData cswl = loadCswl();
190         AbsoluteDate date = new AbsoluteDate(2000, 1, 1, 0, 0, 0.0, utc);
191         final double dailyFlux = cswl.getDailyFlux(date);
192         // The daily flux is supposed to be the one from 1999-12-31
193         assertThat(dailyFlux, closeTo(130.1, 1e-10));
194     }
195 
196     @Test
197     public void testDailyFluxMonthlyPredicted() {
198         CssiSpaceWeatherData cswl = loadCswl();
199         AbsoluteDate date = new AbsoluteDate(2034, 6, 17, 0, 0, 0.0, utc);
200         final double dailyFlux = cswl.getDailyFlux(date);
201         assertThat(dailyFlux, closeTo((132.7 + 137.3) / 2, 1e-3));
202     }
203 
204     @Test
205     public void testMeanFlux() {
206         CssiSpaceWeatherData cswl = loadCswl();
207         AbsoluteDate date = new AbsoluteDate(2000, 1, 1, 0, 0, 0.0, utc);
208         final double meanFlux = cswl.getMeanFlux(date);
209         assertThat(meanFlux, closeTo(165.6, 1e-10));
210     }
211 
212     @Test
213     public void testMeanFluxMonthlyPredicted() {
214         CssiSpaceWeatherData cswl = loadCswl();
215         AbsoluteDate date = new AbsoluteDate(2034, 6, 16, 0, 0, 0.0, utc);
216         final double meanFlux = cswl.getMeanFlux(date);
217         assertThat(meanFlux, closeTo((132.1 + 134.9) / 2, 1e-3));
218     }
219 
220     @Test
221     public void testAverageFlux() {
222         CssiSpaceWeatherData cswl = loadCswl();
223         AbsoluteDate date = new AbsoluteDate(2000, 1, 1, 0, 0, 0.0, utc);
224         final double averageFlux = cswl.getAverageFlux(date);
225         assertThat(averageFlux, closeTo(165.6, 1e-10));
226     }
227 
228     @Test
229     public void testAverageFluxMonthlyPredicted() {
230         CssiSpaceWeatherData cswl = loadCswl();
231         AbsoluteDate date = new AbsoluteDate(2034, 6, 16, 0, 0, 0.0, utc);
232         final double averageFlux = cswl.getAverageFlux(date);
233         assertThat(averageFlux, closeTo((132.1 + 134.9) / 2, 1e-3));
234     }
235 
236     @Test
237     public void testInstantFlux() {
238         CssiSpaceWeatherData cswl = loadCswl();
239         AbsoluteDate date = new AbsoluteDate(2000, 1, 1, 12, 0, 0.0, utc);
240         final double instantFlux = cswl.getInstantFlux(date);
241         assertThat(instantFlux, closeTo((129.9 + 132.9) / 2, 1e-10));
242     }
243 
244     /**
245      * This is to test that daily Kp values with three figures are correctly parsed
246      */
247     @Test
248     public void testDailyKp2003SolarStorm() {
249         CssiSpaceWeatherData cswl = loadCswl();
250         AbsoluteDate date = new AbsoluteDate(2003, 10, 29, 23, 0, 0.0, utc);
251         final double kp = cswl.get24HoursKp(date);
252         assertThat(kp, closeTo(583.0 / 10.0 / 8, 1e-10));
253     }
254 
255     /**
256      * This is to test that Ap values with three figures are correctly parsed
257      */
258     @Test
259     public void testAp2003SolarStorm() {
260         CssiSpaceWeatherData cswl = loadCswl();
261         AbsoluteDate date = new AbsoluteDate(2003, 10, 29, 23, 0, 0.0, utc);
262         final double[] ap = cswl.getAp(date);
263         assertThat(ap[0], closeTo(204, 1e-10));
264         assertThat(ap[1], closeTo(300, 1e-10));
265         assertThat(ap[2], closeTo(300, 1e-10));
266         assertThat(ap[3], closeTo(179, 1e-10));
267         assertThat(ap[4], closeTo(179, 1e-10));
268     }
269 
270     /**
271      * Check integration error is small when integrating the same equations over the same
272      * interval.
273      */
274     @Test
275     public void testWithPropagator() {
276         CelestialBody sun = CelestialBodyFactory.getSun();
277         final Frame eci = FramesFactory.getGCRF();
278         final Frame ecef = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
279         AbsoluteDate date = new AbsoluteDate(2004, 1, 1, utc);
280         OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
281                 Constants.WGS84_EARTH_FLATTENING, ecef);
282         Orbit orbit = new KeplerianOrbit(6378137 + 400e3, 1e-3, FastMath.toRadians(50), 0, 0, 0, PositionAngle.TRUE,
283                 eci, date, Constants.EIGEN5C_EARTH_MU);
284         final SpacecraftState ic = new SpacecraftState(orbit);
285 
286         final AbsoluteDate end = date.shiftedBy(5 * Constants.JULIAN_DAY);
287         final AbsoluteDate resetDate = date.shiftedBy(0.8 * Constants.JULIAN_DAY + 0.1);
288 
289         final SpacecraftState[] lastState = new SpacecraftState[1];
290         final OrekitStepHandler stepSaver = interpolator -> {
291             final AbsoluteDate start = interpolator.getPreviousState().getDate();
292             if (start.compareTo(resetDate) < 0) {
293                 lastState[0] = interpolator.getPreviousState();
294             }
295         };
296 
297         // propagate with state rest to take slightly different path
298         NumericalPropagator propagator = getNumericalPropagatorWithDTM(sun, earth, ic);
299         propagator.setStepHandler(stepSaver);
300         propagator.propagate(resetDate);
301         propagator.resetInitialState(lastState[0]);
302         propagator.clearStepHandlers();
303         SpacecraftState actual = propagator.propagate(end);
304 
305         // propagate straight through
306         propagator = getNumericalPropagatorWithDTM(sun, earth, ic);
307         propagator.resetInitialState(ic);
308         propagator.clearStepHandlers();
309         SpacecraftState expected = propagator.propagate(end);
310 
311         assertThat(actual.getPVCoordinates(), pvCloseTo(expected.getPVCoordinates(), 1.0));
312 
313         // propagate with state rest to take slightly different path
314         propagator = getNumericalPropagatorWithMSIS(sun, earth, ic);
315         propagator.setStepHandler(stepSaver);
316         propagator.propagate(resetDate);
317         propagator.resetInitialState(lastState[0]);
318         propagator.clearStepHandlers();
319         actual = propagator.propagate(end);
320 
321         // propagate straight through
322         propagator = getNumericalPropagatorWithMSIS(sun, earth, ic);
323         propagator.resetInitialState(ic);
324         propagator.clearStepHandlers();
325         expected = propagator.propagate(end);
326 
327         assertThat(actual.getPVCoordinates(), pvCloseTo(expected.getPVCoordinates(), 1.0));
328     }
329 
330     /**
331      * Configure a numerical propagator with DTM2000 atmosphere.
332      *
333      * @param sun   Sun.
334      * @param earth Earth.
335      * @param ic    initial condition.
336      * @return a propagator with DTM2000 atmosphere.
337      */
338     private NumericalPropagator getNumericalPropagatorWithDTM(CelestialBody sun, OneAxisEllipsoid earth,
339             SpacecraftState ic) {
340         // some non-integer step size to induce truncation error in flux interpolation
341         final ODEIntegrator integrator = new ClassicalRungeKuttaIntegrator(120 + 0.1);
342         NumericalPropagator propagator = new NumericalPropagator(integrator);
343         DTM2000InputParameters flux = loadCswl();
344         final Atmosphere atmosphere = new DTM2000(flux, sun, earth);
345         final IsotropicDrag satellite = new IsotropicDrag(1, 3.2);
346         propagator.addForceModel(new DragForce(atmosphere, satellite));
347 
348         propagator.setInitialState(ic);
349         propagator.setOrbitType(OrbitType.CARTESIAN);
350 
351         return propagator;
352     }
353 
354     /**
355      * Configure a numerical propagator with NRLMSISE00 atmosphere.
356      *
357      * @param sun   Sun.
358      * @param earth Earth.
359      * @param ic    initial condition.
360      * @return a propagator with NRLMSISE00 atmosphere.
361      */
362     private NumericalPropagator getNumericalPropagatorWithMSIS(CelestialBody sun, OneAxisEllipsoid earth,
363             SpacecraftState ic) {
364         // some non-integer step size to induce truncation error in flux interpolation
365         final ODEIntegrator integrator = new ClassicalRungeKuttaIntegrator(120 + 0.1);
366         NumericalPropagator propagator = new NumericalPropagator(integrator);
367         NRLMSISE00InputParameters flux = loadCswl();
368         final Atmosphere atmosphere = new NRLMSISE00(flux, sun, earth);
369         final IsotropicDrag satellite = new IsotropicDrag(1, 3.2);
370         propagator.addForceModel(new DragForce(atmosphere, satellite));
371 
372         propagator.setInitialState(ic);
373         propagator.setOrbitType(OrbitType.CARTESIAN);
374 
375         return propagator;
376     }
377 }