1   /* Copyright 2013-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.rugged.api;
18  
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.EOFException;
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.StreamCorruptedException;
27  import java.lang.reflect.Field;
28  import java.net.URISyntaxException;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.List;
32  
33  import org.hipparchus.geometry.euclidean.threed.Rotation;
34  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
35  import org.hipparchus.geometry.euclidean.threed.Vector3D;
36  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
37  import org.hipparchus.util.FastMath;
38  import org.junit.jupiter.api.Assertions;
39  import org.junit.jupiter.api.Test;
40  import org.junit.jupiter.api.io.TempDir;
41  import org.orekit.attitudes.AttitudeProvider;
42  import org.orekit.attitudes.NadirPointing;
43  import org.orekit.attitudes.YawCompensation;
44  import org.orekit.bodies.BodyShape;
45  import org.orekit.bodies.CelestialBodyFactory;
46  import org.orekit.bodies.GeodeticPoint;
47  import org.orekit.bodies.OneAxisEllipsoid;
48  import org.orekit.data.DataContext;
49  import org.orekit.data.DirectoryCrawler;
50  import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
51  import org.orekit.forces.gravity.ThirdBodyAttraction;
52  import org.orekit.forces.gravity.potential.GravityFieldFactory;
53  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
54  import org.orekit.frames.Frame;
55  import org.orekit.frames.FramesFactory;
56  import org.orekit.frames.Transform;
57  import org.orekit.orbits.CircularOrbit;
58  import org.orekit.orbits.Orbit;
59  import org.orekit.orbits.OrbitType;
60  import org.orekit.orbits.PositionAngleType;
61  import org.orekit.propagation.CartesianToleranceProvider;
62  import org.orekit.propagation.Propagator;
63  import org.orekit.propagation.SpacecraftState;
64  import org.orekit.propagation.ToleranceProvider;
65  import org.orekit.propagation.analytical.KeplerianPropagator;
66  import org.orekit.propagation.numerical.NumericalPropagator;
67  import org.orekit.rugged.errors.RuggedException;
68  import org.orekit.rugged.errors.RuggedMessages;
69  import org.orekit.rugged.linesensor.LineDatation;
70  import org.orekit.rugged.linesensor.LineSensor;
71  import org.orekit.rugged.linesensor.LinearLineDatation;
72  import org.orekit.rugged.los.LOSBuilder;
73  import org.orekit.rugged.los.TimeDependentLOS;
74  import org.orekit.rugged.raster.RandomLandscapeUpdater;
75  import org.orekit.rugged.raster.TileUpdater;
76  import org.orekit.rugged.raster.VolcanicConeElevationUpdater;
77  import org.orekit.rugged.refraction.AtmosphericRefraction;
78  import org.orekit.rugged.refraction.ConstantRefractionLayer;
79  import org.orekit.rugged.refraction.MultiLayerModel;
80  import org.orekit.rugged.utils.ExtendedEllipsoid;
81  import org.orekit.time.AbsoluteDate;
82  import org.orekit.time.TimeScale;
83  import org.orekit.time.TimeScalesFactory;
84  import org.orekit.utils.AngularDerivativesFilter;
85  import org.orekit.utils.CartesianDerivativesFilter;
86  import org.orekit.utils.Constants;
87  import org.orekit.utils.IERSConventions;
88  import org.orekit.utils.PVCoordinates;
89  import org.orekit.utils.TimeStampedAngularCoordinates;
90  import org.orekit.utils.TimeStampedPVCoordinates;
91  
92  public class RuggedBuilderTest {
93  
94      @TempDir
95      public File tempFolder;
96  
97      @Test
98      public void testSetContextWithEphemerides()
99          throws URISyntaxException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
100 
101         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
102         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
103         AbsoluteDate t0 = new AbsoluteDate("2012-01-01T00:00:00", TimeScalesFactory.getUTC());
104 
105         List<TimeStampedPVCoordinates> pv = Arrays.asList(
106             createPV(t0, 0.000, -1545168.478, -7001985.361,       0.000, -1095.152224, 231.344922, -7372.851944),
107             createPV(t0, 1.000, -1546262.794, -7001750.226,   -7372.851, -1093.478904, 238.925123, -7372.847995),
108             createPV(t0, 2.000, -1547355.435, -7001507.511,  -14745.693, -1091.804408, 246.505033, -7372.836044),
109             createPV(t0, 3.000, -1548446.402, -7001257.216,  -22118.520, -1090.128736, 254.084644, -7372.816090),
110             createPV(t0, 4.000, -1549535.693, -7000999.342,  -29491.323, -1088.451892, 261.663949, -7372.788133),
111             createPV(t0, 5.000, -1550623.306, -7000733.888,  -36864.094, -1086.773876, 269.242938, -7372.752175),
112             createPV(t0, 6.000, -1551709.240, -7000460.856,  -44236.825, -1085.094690, 276.821604, -7372.708214),
113             createPV(t0, 7.000, -1552793.495, -7000180.245,  -51609.507, -1083.414336, 284.399938, -7372.656251),
114             createPV(t0, 8.000, -1553876.068, -6999892.056,  -58982.134, -1081.732817, 291.977932, -7372.596287),
115             createPV(t0, 9.000, -1554956.960, -6999596.289,  -66354.697, -1080.050134, 299.555578, -7372.528320),
116             createPV(t0,10.000, -1556036.168, -6999292.945,  -73727.188, -1078.366288, 307.132868, -7372.452352),
117             createPV(t0,11.000, -1557113.692, -6998982.024,  -81099.599, -1076.681282, 314.709792, -7372.368382),
118             createPV(t0,12.000, -1558189.530, -6998663.526,  -88471.922, -1074.995118, 322.286344, -7372.276411),
119             createPV(t0,13.000, -1559263.682, -6998337.451,  -95844.150, -1073.307797, 329.862513, -7372.176439),
120             createPV(t0,14.000, -1560336.145, -6998003.801, -103216.273, -1071.619321, 337.438294, -7372.068466),
121             createPV(t0,15.000, -1561406.920, -6997662.575, -110588.284, -1069.929692, 345.013676, -7371.952492),
122             createPV(t0,16.000, -1562476.004, -6997313.774, -117960.175, -1068.238912, 352.588652, -7371.828517),
123             createPV(t0,17.000, -1563543.398, -6996957.398, -125331.938, -1066.546983, 360.163213, -7371.696542),
124             createPV(t0,18.000, -1564609.098, -6996593.447, -132703.565, -1064.853906, 367.737352, -7371.556566),
125             createPV(t0,19.000, -1565673.105, -6996221.923, -140075.049, -1063.159684, 375.311060, -7371.408591),
126             createPV(t0,20.000, -1566735.417, -6995842.825, -147446.380, -1061.464319, 382.884328, -7371.252616));
127 
128         List<TimeStampedAngularCoordinates> q = Arrays.asList(
129             createQ(t0, 0.000, 0.516354347549, -0.400120145429,  0.583012133139,  0.483093065155),
130             createQ(t0, 1.000, 0.516659035405, -0.399867643627,  0.582741754688,  0.483302551263),
131             createQ(t0, 2.000, 0.516963581177, -0.399615033309,  0.582471217473,  0.483511904409),
132             createQ(t0, 3.000, 0.517267984776, -0.399362314553,  0.582200521577,  0.483721124530),
133             createQ(t0, 4.000, 0.517572246112, -0.399109487434,  0.581929667081,  0.483930211565),
134             createQ(t0, 5.000, 0.517876365096, -0.398856552030,  0.581658654071,  0.484139165451),
135             createQ(t0, 6.000, 0.518180341637, -0.398603508416,  0.581387482627,  0.484347986126),
136             createQ(t0, 7.000, 0.518484175647, -0.398350356669,  0.581116152834,  0.484556673529),
137             createQ(t0, 8.000, 0.518787867035, -0.398097096866,  0.580844664773,  0.484765227599),
138             createQ(t0, 9.000, 0.519091415713, -0.397843729083,  0.580573018530,  0.484973648272),
139             createQ(t0,10.000, 0.519394821590, -0.397590253397,  0.580301214186,  0.485181935488),
140             createQ(t0,11.000, 0.519698084578, -0.397336669885,  0.580029251825,  0.485390089185),
141             createQ(t0,12.000, 0.520001204587, -0.397082978623,  0.579757131530,  0.485598109301),
142             createQ(t0,13.000, 0.520304181527, -0.396829179688,  0.579484853385,  0.485805995775),
143             createQ(t0,14.000, 0.520607015311, -0.396575273158,  0.579212417473,  0.486013748545),
144             createQ(t0,15.000, 0.520909705847, -0.396321259108,  0.578939823877,  0.486221367550),
145             createQ(t0,16.000, 0.521212253049, -0.396067137616,  0.578667072681,  0.486428852729),
146             createQ(t0,17.000, 0.521514656825, -0.395812908759,  0.578394163969,  0.486636204020),
147             createQ(t0,18.000, 0.521816917089, -0.395558572613,  0.578121097824,  0.486843421362),
148             createQ(t0,19.000, 0.522119033749, -0.395304129256,  0.577847874330,  0.487050504694),
149             createQ(t0,20.000, 0.522421006719, -0.395049578765,  0.577574493570,  0.487257453954));
150 
151         TileUpdater updater =
152                 new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667),
153                                                                    FastMath.toRadians(123.685),
154                                                                    2463.0),
155                                                  FastMath.toRadians(30.0), 16.0,
156                                                  FastMath.toRadians(1.0), 1201);
157 
158         RuggedBuilder builder = new RuggedBuilder().
159                                 setDigitalElevationModel(updater, 8).
160                                 setAlgorithm(AlgorithmId.DUVENHAGE).
161                                 setTimeSpan(pv.get(0).getDate(), pv.get(pv.size() - 1).getDate(), 0.001, 5.0);
162         try {
163             builder.build();
164             Assertions.fail("an exception should have been thrown");
165         } catch (RuggedException re) {
166             Assertions.assertEquals(RuggedMessages.UNINITIALIZED_CONTEXT, re.getSpecifier());
167             Assertions.assertEquals("RuggedBuilder.setEllipsoid()", re.getParts()[0]);
168         }
169         
170         Assertions.assertTrue(builder.isOverlappingTiles());
171         builder.setOverlappingTiles(false);
172         Assertions.assertTrue(!builder.isOverlappingTiles());
173         
174         builder.setEllipsoid(EllipsoidId.GRS80, BodyRotatingFrameId.ITRF);
175         try {
176             builder.build();
177             Assertions.fail("an exception should have been thrown");
178         } catch (RuggedException re) {
179             Assertions.assertEquals(RuggedMessages.UNINITIALIZED_CONTEXT, re.getSpecifier());
180             Assertions.assertEquals("RuggedBuilder.setTrajectory()", re.getParts()[0]);
181         }
182         builder.setTrajectory(InertialFrameId.EME2000,
183                               pv, 8, CartesianDerivativesFilter.USE_PV,
184                               q, 8, AngularDerivativesFilter.USE_R);
185         Assertions.assertSame(FramesFactory.getEME2000(), builder.getInertialFrame());
186 
187         // light time correction and aberration of light correction are enabled by default
188         Rugged rugged = builder.build();
189         Assertions.assertTrue(rugged.isLightTimeCorrected());
190         Assertions.assertTrue(rugged.isAberrationOfLightCorrected());
191         Assertions.assertTrue(builder.getLightTimeCorrection());
192         Assertions.assertTrue(builder.getAberrationOfLightCorrection());
193 
194         builder.setLightTimeCorrection(false);
195         rugged = builder.build();
196         Assertions.assertFalse(rugged.isLightTimeCorrected());
197         Assertions.assertTrue(rugged.isAberrationOfLightCorrected());
198         Assertions.assertFalse(builder.getLightTimeCorrection());
199         Assertions.assertTrue(builder.getAberrationOfLightCorrection());
200 
201         builder.setAberrationOfLightCorrection(false);
202         rugged = builder.build();
203         Assertions.assertFalse(rugged.isLightTimeCorrected());
204         Assertions.assertFalse(rugged.isAberrationOfLightCorrected());
205         Assertions.assertFalse(builder.getLightTimeCorrection());
206         Assertions.assertFalse(builder.getAberrationOfLightCorrection());
207         
208         AtmosphericRefraction atmosphericRefraction = new MultiLayerModel(builder.getEllipsoid());
209         builder.setRefractionCorrection(atmosphericRefraction);
210         rugged = builder.build();
211         
212         MultiLayerModel atmosphericRefractionFromBuilder = (MultiLayerModel) builder.getRefractionCorrection();
213         Field atmos = atmosphericRefractionFromBuilder.getClass().getDeclaredField("ellipsoid");
214         atmos.setAccessible(true);
215         ExtendedEllipsoid ellipsoidAtmos = (ExtendedEllipsoid) atmos.get(atmosphericRefractionFromBuilder);
216         Assertions.assertEquals(builder.getEllipsoid().getEquatorialRadius(), ellipsoidAtmos.getEquatorialRadius(), 1.0e-9);
217         Assertions.assertEquals(builder.getEllipsoid().getFlattening(), ellipsoidAtmos.getFlattening(), 1.0e-10);
218 
219         Field layers = atmosphericRefractionFromBuilder.getClass().getDeclaredField("refractionLayers");
220         layers.setAccessible(true);
221         @SuppressWarnings("unchecked")
222         List<ConstantRefractionLayer> layersAtmos = (List<ConstantRefractionLayer>) layers.get(atmosphericRefractionFromBuilder);
223         
224         Field layersExpected = atmosphericRefraction.getClass().getDeclaredField("refractionLayers");
225         layersExpected.setAccessible(true);
226         @SuppressWarnings("unchecked")
227         List<ConstantRefractionLayer> layersAtmosExpected = (List<ConstantRefractionLayer>) layersExpected.get(atmosphericRefraction);
228         Assertions.assertEquals(layersAtmosExpected.size(), layersAtmos.size());
229         
230         List<ConstantRefractionLayer> copyAtmosExpected = new ArrayList<ConstantRefractionLayer>(layersAtmosExpected);
231         List<ConstantRefractionLayer> copyAtmos = new ArrayList<ConstantRefractionLayer>(layersAtmos);
232         
233         Assertions.assertTrue(copyAtmosExpected.removeAll(layersAtmos) && copyAtmos.removeAll(layersAtmosExpected));
234         Assertions.assertTrue(copyAtmosExpected.isEmpty() && copyAtmos.isEmpty());
235         
236         
237         Assertions.assertEquals(AlgorithmId.DUVENHAGE, builder.getAlgorithm());
238         Assertions.assertEquals(6378137.0, builder.getEllipsoid().getEquatorialRadius(), 1.0e-9);
239         Assertions.assertEquals(1.0 / 298.257222101, builder.getEllipsoid().getFlattening(), 1.0e-10);
240         Assertions.assertSame(updater, builder.getTileUpdater());
241         Assertions.assertEquals(8, builder.getMaxCachedTiles());
242         Assertions.assertTrue(Double.isNaN(builder.getConstantElevation()));
243         Assertions.assertEquals(pv.get(0).getDate(), builder.getMinDate());
244         Assertions.assertEquals(pv.get(pv.size() - 1).getDate(), builder.getMaxDate());
245         Assertions.assertEquals(0.001, builder.getTStep(), 1.0e-10);
246         Assertions.assertEquals(5.0, builder.getOvershootTolerance(), 1.0e-10);
247         Assertions.assertSame(FramesFactory.getEME2000(), builder.getInertialFrame());
248         Assertions.assertSame(pv, builder.getPositionsVelocities());
249         Assertions.assertEquals(8, builder.getPVInterpolationNumber());
250         Assertions.assertEquals(CartesianDerivativesFilter.USE_PV, builder.getPVFilter());
251         Assertions.assertSame(q,  builder.getQuaternions());
252         Assertions.assertEquals(8, builder.getAInterpolationNumber());
253         Assertions.assertEquals(AngularDerivativesFilter.USE_R, builder.getAFilter());
254 
255         Assertions.assertTrue(builder.getLineSensors().isEmpty());
256         LineSensor lineSensor = new LineSensor("line", new LinearLineDatation(t0, 2000 / 2, 1.0 / 1.5e-3),
257                                                Vector3D.ZERO,
258                                                createLOSPerfectLine(Vector3D.PLUS_K,
259                                                                     Vector3D.PLUS_I, FastMath.toRadians(1.0), 2000));
260         builder.addLineSensor(lineSensor);
261         Assertions.assertEquals(1, builder.getLineSensors().size());
262         Assertions.assertSame(lineSensor, builder.getLineSensors().get(0));
263         builder.clearLineSensors();
264         Assertions.assertTrue(builder.getLineSensors().isEmpty());
265 
266         builder.setTrajectory(InertialFrameId.GCRF,
267                               pv, 8, CartesianDerivativesFilter.USE_PV,
268                               q, 8, AngularDerivativesFilter.USE_R);
269         Assertions.assertSame(FramesFactory.getGCRF(), builder.getInertialFrame());
270         builder.setTrajectory(InertialFrameId.MOD,
271                               pv, 8, CartesianDerivativesFilter.USE_PV,
272                               q, 8, AngularDerivativesFilter.USE_R);
273         Assertions.assertSame(FramesFactory.getMOD(IERSConventions.IERS_1996), builder.getInertialFrame());
274         builder.setTrajectory(InertialFrameId.TOD,
275                               pv, 8, CartesianDerivativesFilter.USE_PV,
276                               q, 8, AngularDerivativesFilter.USE_R);
277         Assertions.assertSame(FramesFactory.getTOD(IERSConventions.IERS_1996, true), builder.getInertialFrame());
278         builder.setTrajectory(InertialFrameId.VEIS1950,
279                               pv, 8, CartesianDerivativesFilter.USE_PV,
280                               q, 8, AngularDerivativesFilter.USE_R);
281         Assertions.assertSame(FramesFactory.getVeis1950(), builder.getInertialFrame());
282 
283         builder.setAlgorithm(null);
284         try {
285             builder.build();
286             Assertions.fail("an exception should have been thrown");
287         } catch (RuggedException re) {
288             Assertions.assertEquals(RuggedMessages.UNINITIALIZED_CONTEXT, re.getSpecifier());
289             Assertions.assertEquals("RuggedBuilder.setAlgorithmID()", re.getParts()[0]);
290         }
291 
292         builder.setAlgorithm(AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID);
293         builder.setConstantElevation(Double.NaN);
294         try {
295             builder.build();
296             Assertions.fail("an exception should have been thrown");
297         } catch (RuggedException re) {
298             Assertions.assertEquals(RuggedMessages.UNINITIALIZED_CONTEXT, re.getSpecifier());
299             Assertions.assertEquals("RuggedBuilder.setConstantElevation()", re.getParts()[0]);
300         }
301         builder.setConstantElevation(100.0);
302         Assertions.assertNotNull(builder.build());
303 
304         builder.setAlgorithm(AlgorithmId.BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY);
305         builder.setDigitalElevationModel(null, 8);
306         try {
307             builder.build();
308             Assertions.fail("an exception should have been thrown");
309         } catch (RuggedException re) {
310             Assertions.assertEquals(RuggedMessages.UNINITIALIZED_CONTEXT, re.getSpecifier());
311             Assertions.assertEquals("RuggedBuilder.setDigitalElevationModel()", re.getParts()[0]);
312         }
313         builder.setDigitalElevationModel(updater, 8);
314         Assertions.assertNotNull(builder.build());
315 
316     }
317 
318     @Test
319     public void testSetContextWithPropagator()
320         throws URISyntaxException {
321 
322         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
323         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
324         BodyShape  earth                                  = createEarth();
325         NormalizedSphericalHarmonicsProvider gravityField = createGravityField();
326         Orbit      orbit                                  = createOrbit(gravityField.getMu());
327         Propagator propagator                             = createPropagator(earth, gravityField, orbit);
328 
329         TileUpdater updater =
330                 new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667),
331                                                                    FastMath.toRadians(123.685),
332                                                                    2463.0),
333                                                  FastMath.toRadians(30.0), 16.0,
334                                                  FastMath.toRadians(1.0), 1201);
335 
336         RuggedBuilder builder = new RuggedBuilder().
337                 setDigitalElevationModel(updater, 8).
338                 setAlgorithm(AlgorithmId.DUVENHAGE).
339                 setEllipsoid(EllipsoidId.IERS96, BodyRotatingFrameId.ITRF).
340                 setTimeSpan(orbit.getDate().shiftedBy(-10.0), orbit.getDate().shiftedBy(+10.0), 0.001, 5.0).
341                 setTrajectory(1.0, 8, CartesianDerivativesFilter.USE_PV, AngularDerivativesFilter.USE_R, propagator);
342 
343         // light time correction and aberration of light correction are enabled by default
344         Rugged rugged = builder.build();
345         Assertions.assertTrue(rugged.isLightTimeCorrected());
346         Assertions.assertTrue(rugged.isAberrationOfLightCorrected());
347 
348         builder.setLightTimeCorrection(false);
349         rugged = builder.build();
350         Assertions.assertFalse(rugged.isLightTimeCorrected());
351         Assertions.assertTrue(rugged.isAberrationOfLightCorrected());
352 
353         builder.setAberrationOfLightCorrection(false);
354         rugged = builder.build();
355         Assertions.assertFalse(rugged.isLightTimeCorrected());
356         Assertions.assertFalse(rugged.isAberrationOfLightCorrected());
357         Assertions.assertEquals(orbit.getDate().shiftedBy(-10.0), rugged.getMinDate());
358         Assertions.assertEquals(orbit.getDate().shiftedBy(+10.0), rugged.getMaxDate());
359         Assertions.assertEquals(0, rugged.getLineSensors().size());
360 
361     }
362 
363     @Test
364     public void testOutOfTimeRange()
365         throws URISyntaxException {
366 
367         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
368         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
369         AbsoluteDate t0 = new AbsoluteDate("2012-01-01T00:00:00", TimeScalesFactory.getUTC());
370 
371         List<TimeStampedPVCoordinates> pv = Arrays.asList(
372             createPV(t0, 0.000, -1545168.478, -7001985.361,       0.000, -1095.152224, 231.344922, -7372.851944),
373             createPV(t0, 1.000, -1546262.794, -7001750.226,   -7372.851, -1093.478904, 238.925123, -7372.847995),
374             createPV(t0, 2.000, -1547355.435, -7001507.511,  -14745.693, -1091.804408, 246.505033, -7372.836044),
375             createPV(t0, 3.000, -1548446.402, -7001257.216,  -22118.520, -1090.128736, 254.084644, -7372.816090),
376             createPV(t0, 4.000, -1549535.693, -7000999.342,  -29491.323, -1088.451892, 261.663949, -7372.788133),
377             createPV(t0, 5.000, -1550623.306, -7000733.888,  -36864.094, -1086.773876, 269.242938, -7372.752175),
378             createPV(t0, 6.000, -1551709.240, -7000460.856,  -44236.825, -1085.094690, 276.821604, -7372.708214),
379             createPV(t0, 7.000, -1552793.495, -7000180.245,  -51609.507, -1083.414336, 284.399938, -7372.656251),
380             createPV(t0, 8.000, -1553876.068, -6999892.056,  -58982.134, -1081.732817, 291.977932, -7372.596287),
381             createPV(t0, 9.000, -1554956.960, -6999596.289,  -66354.697, -1080.050134, 299.555578, -7372.528320),
382             createPV(t0,10.000, -1556036.168, -6999292.945,  -73727.188, -1078.366288, 307.132868, -7372.452352));
383 
384         List<TimeStampedAngularCoordinates> q = Arrays.asList(
385             createQ(t0, 4.000, 0.517572246112, -0.399109487434,  0.581929667081,  0.483930211565),
386             createQ(t0, 5.000, 0.517876365096, -0.398856552030,  0.581658654071,  0.484139165451),
387             createQ(t0, 6.000, 0.518180341637, -0.398603508416,  0.581387482627,  0.484347986126),
388             createQ(t0, 7.000, 0.518484175647, -0.398350356669,  0.581116152834,  0.484556673529),
389             createQ(t0, 8.000, 0.518787867035, -0.398097096866,  0.580844664773,  0.484765227599));
390 
391         TileUpdater updater =
392                 new VolcanicConeElevationUpdater(new GeodeticPoint(FastMath.toRadians(13.25667),
393                                                                    FastMath.toRadians(123.685),
394                                                                    2463.0),
395                                                  FastMath.toRadians(30.0), 16.0,
396                                                  FastMath.toRadians(1.0), 1201);
397 
398         Assertions.assertNotNull(new RuggedBuilder().
399                              setDigitalElevationModel(updater, 8).
400                              setAlgorithm(AlgorithmId.DUVENHAGE).
401                              setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
402                              setTimeSpan(t0.shiftedBy(4), t0.shiftedBy(6), 0.001, 5.0).
403                              setTrajectory(InertialFrameId.EME2000,
404                                            pv,2, CartesianDerivativesFilter.USE_PV,
405                                            q, 2, AngularDerivativesFilter.USE_R).
406                              build());
407         try {
408             new RuggedBuilder().
409             setDigitalElevationModel(updater, 8).
410             setAlgorithm(AlgorithmId.DUVENHAGE).
411             setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
412             setTimeSpan(t0.shiftedBy(-1), t0.shiftedBy(6), 0.001, 0.0001).
413             setTrajectory(InertialFrameId.EME2000,
414                           pv, 2, CartesianDerivativesFilter.USE_PV,
415                           q, 2, AngularDerivativesFilter.USE_R);
416         } catch (RuggedException re) {
417             Assertions.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
418             Assertions.assertEquals(t0.shiftedBy(-1), re.getParts()[0]);
419         }
420 
421         try {
422             new RuggedBuilder().
423             setDigitalElevationModel(updater, 8).
424             setAlgorithm(AlgorithmId.DUVENHAGE).
425             setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
426             setTimeSpan(t0.shiftedBy(2), t0.shiftedBy(6), 0.001, 0.0001).
427             setTrajectory(InertialFrameId.EME2000,
428                           pv, 2, CartesianDerivativesFilter.USE_PV,
429                           q, 2, AngularDerivativesFilter.USE_R);
430         } catch (RuggedException re) {
431             Assertions.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
432             Assertions.assertEquals(t0.shiftedBy(2), re.getParts()[0]);
433         }
434 
435         try {
436             new RuggedBuilder().
437             setDigitalElevationModel(updater, 8).
438             setAlgorithm(AlgorithmId.DUVENHAGE).
439             setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
440             setTimeSpan(t0.shiftedBy(4), t0.shiftedBy(9), 0.001, 0.0001).
441             setTrajectory(InertialFrameId.EME2000,
442                           pv, 2, CartesianDerivativesFilter.USE_PV,
443                           q, 2, AngularDerivativesFilter.USE_R);
444         } catch (RuggedException re) {
445             Assertions.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
446             Assertions.assertEquals(t0.shiftedBy(9), re.getParts()[0]);
447         }
448 
449         try {
450             new RuggedBuilder().
451             setDigitalElevationModel(updater, 8).
452             setAlgorithm(AlgorithmId.DUVENHAGE).
453             setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
454             setTimeSpan(t0.shiftedBy(4), t0.shiftedBy(12), 0.001, 0.0001).
455             setTrajectory(InertialFrameId.EME2000,
456                           pv, 2, CartesianDerivativesFilter.USE_PV,
457                           q, 2, AngularDerivativesFilter.USE_R);
458         } catch (RuggedException re) {
459             Assertions.assertEquals(RuggedMessages.OUT_OF_TIME_RANGE, re.getSpecifier());
460             Assertions.assertEquals(t0.shiftedBy(12), re.getParts()[0]);
461         }
462 
463     }
464 
465     @Test
466     public void testInterpolatorDump()
467         throws URISyntaxException {
468 
469         int dimension = 200;
470 
471         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
472         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
473         final BodyShape  earth = createEarth();
474         final Orbit      orbit = createOrbit(Constants.EIGEN5C_EARTH_MU);
475 
476         AbsoluteDate crossing = new AbsoluteDate("2012-01-01T12:30:00.000", TimeScalesFactory.getUTC());
477 
478         // one line sensor
479         // position: 1.5m in front (+X) and 20 cm above (-Z) of the S/C center of mass
480         // los: swath in the (YZ) plane, looking at 50° roll, ±1° aperture
481         Vector3D position = new Vector3D(1.5, 0, -0.2);
482         TimeDependentLOS los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
483                                                                  FastMath.toRadians(50.0),
484                                                                  RotationConvention.VECTOR_OPERATOR).applyTo(Vector3D.PLUS_K),
485                                                     Vector3D.PLUS_I, FastMath.toRadians(1.0), dimension);
486 
487         // linear datation model: at reference time we get line 100, and the rate is one line every 1.5ms
488         LineDatation lineDatation = new LinearLineDatation(crossing, dimension / 2, 1.0 / 1.5e-3);
489         int firstLine = 0;
490         int lastLine  = dimension;
491         LineSensor lineSensor = new LineSensor("line", lineDatation, position, los);
492         AbsoluteDate minDate = lineSensor.getDate(firstLine);
493         AbsoluteDate maxDate = lineSensor.getDate(lastLine);
494 
495         TileUpdater updater =
496                 new RandomLandscapeUpdater(0.0, 9000.0, 0.5, 0x84186d1344722b8fl,
497                                            FastMath.toRadians(1.0), 257);
498 
499         RuggedBuilder original = new RuggedBuilder().
500                                 setDigitalElevationModel(updater, 8).
501                                 setAlgorithm(AlgorithmId.DUVENHAGE).
502                                 setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
503                                 setTimeSpan(minDate, maxDate, 0.001, 5.0).
504                                 setTrajectory(InertialFrameId.EME2000,
505                                               orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
506                                               8, CartesianDerivativesFilter.USE_PV,
507                                               orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
508                                               2, AngularDerivativesFilter.USE_R).
509                                 addLineSensor(lineSensor);
510 
511         ByteArrayOutputStream bos = new ByteArrayOutputStream();
512         original.storeInterpolator(bos);
513         Assertions.assertTrue(bos.size() > 100000);
514         Assertions.assertTrue(bos.size() < 200000);
515 
516         GeodeticPoint[] gpOriginal = original.build().directLocation("line", 100);
517 
518         RuggedBuilder recovered = new RuggedBuilder().
519                 setDigitalElevationModel(updater, 8).
520                 setAlgorithm(AlgorithmId.DUVENHAGE).
521                 setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
522                 setTrajectoryAndTimeSpan(new ByteArrayInputStream(bos.toByteArray())).
523                 addLineSensor(lineSensor);
524         GeodeticPoint[] gpRecovered = recovered.build().directLocation("line", 100);
525 
526         for (int i = 0; i < gpOriginal.length; ++i) {
527             Vector3D pOriginal  = earth.transform(gpOriginal[i]);
528             Vector3D pRecovered = earth.transform(gpRecovered[i]);
529             Assertions.assertEquals(0.0, Vector3D.distance(pOriginal, pRecovered), 1.0e-15);
530         }
531 
532     }
533 
534     @Test
535     public void testInterpolatorCannotDump()
536         throws URISyntaxException, IOException {
537 
538         int dimension = 200;
539 
540         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
541         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
542         final BodyShape  earth = createEarth();
543         final Orbit      orbit = createOrbit(Constants.EIGEN5C_EARTH_MU);
544 
545         AbsoluteDate crossing = new AbsoluteDate("2012-01-01T12:30:00.000", TimeScalesFactory.getUTC());
546 
547         // one line sensor
548         // position: 1.5m in front (+X) and 20 cm above (-Z) of the S/C center of mass
549         // los: swath in the (YZ) plane, looking at 50° roll, ±1° aperture
550         Vector3D position = new Vector3D(1.5, 0, -0.2);
551         TimeDependentLOS los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
552                                                                  FastMath.toRadians(50.0),
553                                                                  RotationConvention.VECTOR_OPERATOR).applyTo(Vector3D.PLUS_K),
554                                                     Vector3D.PLUS_I, FastMath.toRadians(1.0), dimension);
555 
556         // linear datation model: at reference time we get line 100, and the rate is one line every 1.5ms
557         LineDatation lineDatation = new LinearLineDatation(crossing, dimension / 2, 1.0 / 1.5e-3);
558         int firstLine = 0;
559         int lastLine  = dimension;
560         LineSensor lineSensor = new LineSensor("line", lineDatation, position, los);
561         AbsoluteDate minDate = lineSensor.getDate(firstLine);
562         AbsoluteDate maxDate = lineSensor.getDate(lastLine);
563 
564         RuggedBuilder original = new RuggedBuilder().
565                 setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
566                 setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
567                 setTimeSpan(minDate, maxDate, 0.001, 5.0).
568                 setTrajectory(InertialFrameId.EME2000,
569                               orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
570                               8, CartesianDerivativesFilter.USE_PV,
571                               orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
572                               2, AngularDerivativesFilter.USE_R);
573 
574         FileOutputStream fos = new FileOutputStream(File.createTempFile("junit", null, tempFolder));
575         fos.close();
576         try {
577             original.storeInterpolator(fos);
578             Assertions.fail("an exception should have been thrown");
579         } catch (RuggedException re) {
580             Assertions.assertEquals(IOException.class, re.getCause().getClass());
581         }
582     }
583 
584     @Test
585     public void testInterpolatorDumpWrongFrame()
586         throws URISyntaxException {
587 
588         int dimension = 200;
589 
590         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
591         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
592         final BodyShape  earth = createEarth();
593         final Orbit      orbit = createOrbit(Constants.EIGEN5C_EARTH_MU);
594 
595         AbsoluteDate crossing = new AbsoluteDate("2012-01-01T12:30:00.000", TimeScalesFactory.getUTC());
596 
597         // one line sensor
598         // position: 1.5m in front (+X) and 20 cm above (-Z) of the S/C center of mass
599         // los: swath in the (YZ) plane, looking at 50° roll, ±1° aperture
600         Vector3D position = new Vector3D(1.5, 0, -0.2);
601         TimeDependentLOS los = createLOSPerfectLine(new Rotation(Vector3D.PLUS_I,
602                                                                  FastMath.toRadians(50.0),
603                                                                  RotationConvention.VECTOR_OPERATOR).applyTo(Vector3D.PLUS_K),
604                                                     Vector3D.PLUS_I, FastMath.toRadians(1.0), dimension);
605 
606         // linear datation model: at reference time we get line 100, and the rate is one line every 1.5ms
607         LineDatation lineDatation = new LinearLineDatation(crossing, dimension / 2, 1.0 / 1.5e-3);
608         int firstLine = 0;
609         int lastLine  = dimension;
610         LineSensor lineSensor = new LineSensor("line", lineDatation, position, los);
611         AbsoluteDate minDate = lineSensor.getDate(firstLine);
612         AbsoluteDate maxDate = lineSensor.getDate(lastLine);
613 
614         RuggedBuilder original = new RuggedBuilder().
615                 setDigitalElevationModel(null, -1).
616                 setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
617                 setEllipsoid(EllipsoidId.WGS84, BodyRotatingFrameId.ITRF).
618                 setTimeSpan(minDate, maxDate, 0.001, 5.0).
619                 setTrajectory(InertialFrameId.EME2000,
620                               orbitToPV(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
621                               8, CartesianDerivativesFilter.USE_PV,
622                               orbitToQ(orbit, earth, minDate.shiftedBy(-1.0), maxDate.shiftedBy(+1.0), 0.25),
623                               2, AngularDerivativesFilter.USE_R);
624 
625         ByteArrayOutputStream bos = new ByteArrayOutputStream();
626         original.storeInterpolator(bos);
627         Assertions.assertTrue(bos.size() > 100000);
628         Assertions.assertTrue(bos.size() < 200000);
629 
630         for (BodyRotatingFrameId bId : Arrays.asList(BodyRotatingFrameId.GTOD,
631                                                      BodyRotatingFrameId.ITRF_EQUINOX)) {
632             try {
633                 new RuggedBuilder().
634                 setAlgorithm(AlgorithmId.IGNORE_DEM_USE_ELLIPSOID).
635                 setEllipsoid(EllipsoidId.WGS84, bId).
636                 setTrajectoryAndTimeSpan(new ByteArrayInputStream(bos.toByteArray())).build();
637                 Assertions.fail("an exception should have been thrown");
638             } catch (RuggedException re) {
639                 Assertions.assertEquals(RuggedMessages.FRAMES_MISMATCH_WITH_INTERPOLATOR_DUMP,
640                                     re.getSpecifier());
641             }
642         }
643     }
644 
645     @Test
646     public void testInterpolatorNotADump()
647         throws URISyntaxException {
648 
649         String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath();
650         DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(new File(path)));
651 
652         // the following array is a real serialization file corresponding to the following
653         // made-up empty class that does not exist in Rugged:
654         //        public class NonExistentClass implements java.io.Serializable {
655         //            private static final long serialVersionUID = -1;
656         //        }
657         byte[] nonExistentClass = new byte[] {
658             -84, -19,   0,   5, 115, 114,
659               0,  16,  78, 111, 110,  69,
660             120, 105, 115, 116, 101, 110,
661             116,  67, 108,  97, 115, 115,
662              -1,  -1,  -1,  -1,  -1,  -1,
663              -1,  -1,   2,   0,   0, 120,
664             112
665         };
666 
667         // the following array is a real serialization file of object Integer.valueOf(1)
668         byte[] integerOne = new byte[] {
669           -84, -19,   0,   5, 115, 114,
670             0,  17, 106,  97, 118,  97,
671            46, 108,  97, 110, 103,  46,
672            73, 110, 116, 101, 103, 101,
673           114,  18, -30, -96, -92,  -9,
674          -127,-121,  56,   2,   0,   1,
675            73,   0,   5, 118,  97, 108,
676           117, 101, 120, 114,   0,  16,
677           106,  97, 118,  97,  46, 108,
678            97, 110, 103,  46,  78, 117,
679           109,  98, 101, 114,-122, -84,
680          -107,  29,  11,-108, -32,-117,
681             2,   0,   0, 120, 112,   0,
682             0,   0,   1
683         };
684 
685         // the following array is a truncation of the previous one
686         byte[] truncatedDump = new byte[] {
687           -84, -19,   0,   5, 115, 114,
688             0,  17, 106,  97, 118,  97
689         };
690 
691         byte[] notSerialization = new byte[] {
692             1, 2, 3, 4, 5, 6
693         };
694 
695         for (byte[] array : Arrays.asList(nonExistentClass, integerOne, truncatedDump, notSerialization)) {
696             try {
697                 new RuggedBuilder().setTrajectoryAndTimeSpan(new ByteArrayInputStream(array));
698                 Assertions.fail("an exception should have been thrown");
699             } catch (RuggedException re) {
700                 Assertions.assertEquals(RuggedMessages.NOT_INTERPOLATOR_DUMP_DATA,
701                                     re.getSpecifier());
702                 if (array == nonExistentClass) {
703                     Assertions.assertEquals(ClassNotFoundException.class, re.getCause().getClass());
704                 } else if (array == integerOne) {
705                     Assertions.assertEquals(ClassCastException.class, re.getCause().getClass());
706                 } else if (array == truncatedDump) {
707                     Assertions.assertEquals(EOFException.class, re.getCause().getClass());
708                 } else if (array == notSerialization) {
709                     Assertions.assertEquals(StreamCorruptedException.class, re.getCause().getClass());
710                 }
711             }
712         }
713 
714     }
715 
716     protected void addSatellitePV(TimeScale gps, Frame eme2000, Frame itrf,
717                                   ArrayList<TimeStampedPVCoordinates> satellitePVList,
718                                   String absDate,
719                                   double px, double py, double pz, double vx, double vy, double vz) {
720         AbsoluteDate ephemerisDate = new AbsoluteDate(absDate, gps);
721         Vector3D position = new Vector3D(px, py, pz);
722         Vector3D velocity = new Vector3D(vx, vy, vz);
723         PVCoordinates pvITRF = new PVCoordinates(position, velocity);
724         Transform transform = itrf.getTransformTo(eme2000, ephemerisDate);
725         Vector3D pEME2000 = transform.transformPosition(pvITRF.getPosition());
726         Vector3D vEME2000 = transform.transformVector(pvITRF.getVelocity());
727         satellitePVList.add(new TimeStampedPVCoordinates(ephemerisDate, pEME2000, vEME2000, Vector3D.ZERO));
728     }
729 
730     protected void addSatelliteQ(TimeScale gps, ArrayList<TimeStampedAngularCoordinates> satelliteQList, String absDate, double q0, double q1, double q2,
731             double q3) {
732         AbsoluteDate attitudeDate = new AbsoluteDate(absDate, gps);
733         Rotation rotation = new Rotation(q0, q1, q2, q3, true);
734         TimeStampedAngularCoordinates pair =
735                 new TimeStampedAngularCoordinates(attitudeDate, rotation, Vector3D.ZERO, Vector3D.ZERO);
736         satelliteQList.add(pair);
737     }
738 
739     private BodyShape createEarth() {
740         return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
741                                     Constants.WGS84_EARTH_FLATTENING,
742                                     FramesFactory.getITRF(IERSConventions.IERS_2010, true));
743     }
744 
745     private NormalizedSphericalHarmonicsProvider createGravityField() {
746         return GravityFieldFactory.getNormalizedProvider(12, 12);
747     }
748 
749     private Orbit createOrbit(double mu) {
750         // the following orbital parameters have been computed using
751         // Orekit tutorial about phasing, using the following configuration:
752         //
753         //  orbit.date                          = 2012-01-01T00:00:00.000
754         //  phasing.orbits.number               = 143
755         //  phasing.days.number                 =  10
756         //  sun.synchronous.reference.latitude  = 0
757         //  sun.synchronous.reference.ascending = false
758         //  sun.synchronous.mean.solar.time     = 10:30:00
759         //  gravity.field.degree                = 12
760         //  gravity.field.order                 = 12
761         AbsoluteDate date = new AbsoluteDate("2012-01-01T00:00:00.000", TimeScalesFactory.getUTC());
762         Frame eme2000 = FramesFactory.getEME2000();
763         return new CircularOrbit(7173352.811913891,
764                                  -4.029194321683225E-4, 0.0013530362644647786,
765                                  FastMath.toRadians(98.63218182243709),
766                                  FastMath.toRadians(77.55565567747836),
767                                  FastMath.PI, PositionAngleType.TRUE,
768                                  eme2000, date, mu);
769     }
770 
771     private Propagator createPropagator(BodyShape earth,
772                                         NormalizedSphericalHarmonicsProvider gravityField,
773                                         Orbit orbit) {
774 
775         AttitudeProvider yawCompensation = new YawCompensation(orbit.getFrame(), new NadirPointing(orbit.getFrame(), earth));
776         SpacecraftState state = new SpacecraftState(orbit,
777                                                     yawCompensation.getAttitude(orbit,
778                                                                                 orbit.getDate(),
779                                                                                 orbit.getFrame())).withMass(1180.0);
780 
781         // numerical model for improving orbit
782         OrbitType type = OrbitType.CIRCULAR;
783         double[][] tolerances = ToleranceProvider.of(CartesianToleranceProvider.of(0.1)).getTolerances(orbit, type);
784         DormandPrince853Integrator integrator =
785                 new DormandPrince853Integrator(1.0e-4 * orbit.getKeplerianPeriod(),
786                                                1.0e-1 * orbit.getKeplerianPeriod(),
787                                                tolerances[0], tolerances[1]);
788         integrator.setInitialStepSize(1.0e-2 * orbit.getKeplerianPeriod());
789         NumericalPropagator numericalPropagator = new NumericalPropagator(integrator);
790         numericalPropagator.addForceModel(new HolmesFeatherstoneAttractionModel(earth.getBodyFrame(), gravityField));
791         numericalPropagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getSun()));
792         numericalPropagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getMoon()));
793         numericalPropagator.setOrbitType(type);
794         numericalPropagator.setInitialState(state);
795         numericalPropagator.setAttitudeProvider(yawCompensation);
796         return numericalPropagator;
797 
798     }
799 
800     private TimeDependentLOS createLOSPerfectLine(Vector3D center, Vector3D normal, double halfAperture, int n) {
801         List<Vector3D> list = new ArrayList<Vector3D>(n);
802         for (int i = 0; i < n; ++i) {
803             double alpha = (halfAperture * (2 * i + 1 - n)) / (n - 1);
804             list.add(new Rotation(normal, alpha, RotationConvention.VECTOR_OPERATOR).applyTo(center));
805         }
806         return new LOSBuilder(list).build();
807     }
808 
809     private TimeStampedPVCoordinates createPV(AbsoluteDate t0, double dt,
810                                               double px, double py, double pz,
811                                               double vx, double vy, double vz) {
812         return new TimeStampedPVCoordinates(t0.shiftedBy(dt),
813                                             new Vector3D(px, py, pz),
814                                             new Vector3D(vx, vy, vz),
815                                             Vector3D.ZERO);
816     }
817 
818     private TimeStampedAngularCoordinates createQ(AbsoluteDate t0, double dt,
819                                                        double q0, double q1, double q2, double q3) {
820         return new TimeStampedAngularCoordinates(t0.shiftedBy(dt),
821                                                  new Rotation(q0, q1, q2, q3, true),
822                                                  Vector3D.ZERO, Vector3D.ZERO);
823     }
824 
825     private List<TimeStampedPVCoordinates> orbitToPV(Orbit orbit, BodyShape earth,
826                                                      AbsoluteDate minDate, AbsoluteDate maxDate,
827                                                      double step) {
828         Propagator propagator = new KeplerianPropagator(orbit);
829         propagator.setAttitudeProvider(new YawCompensation(orbit.getFrame(), new NadirPointing(orbit.getFrame(), earth)));
830         propagator.propagate(minDate);
831         final List<TimeStampedPVCoordinates> list = new ArrayList<TimeStampedPVCoordinates>();
832         propagator.getMultiplexer().add(step, currentState -> list.add(new TimeStampedPVCoordinates(currentState.getDate(),
833                                                                                                     currentState.getPVCoordinates().getPosition(),
834                                                                                                     currentState.getPVCoordinates().getVelocity(),
835                                                                                                     Vector3D.ZERO)));
836         propagator.propagate(maxDate);
837         return list;
838     }
839 
840     private List<TimeStampedAngularCoordinates> orbitToQ(Orbit orbit, BodyShape earth,
841                                                          AbsoluteDate minDate, AbsoluteDate maxDate,
842                                                          double step) {
843         
844         Propagator propagator = new KeplerianPropagator(orbit);
845         propagator.setAttitudeProvider(new YawCompensation(orbit.getFrame(), new NadirPointing(orbit.getFrame(), earth)));
846         propagator.propagate(minDate);
847         final List<TimeStampedAngularCoordinates> list = new ArrayList<TimeStampedAngularCoordinates>();
848         propagator.getMultiplexer().add(step, currentState -> list.add(new TimeStampedAngularCoordinates(currentState.getDate(),
849                                                                                                          currentState.getAttitude().getRotation(),
850                                                                                                          Vector3D.ZERO, Vector3D.ZERO)));
851         propagator.propagate(maxDate);
852         return list;
853     }
854 
855 }
856