1   /* Copyright 2002-2022 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.estimation.sequential;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.net.URISyntaxException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.NoSuchElementException;
27  
28  import org.hipparchus.exception.LocalizedCoreFormats;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.linear.MatrixUtils;
31  import org.hipparchus.linear.RealMatrix;
32  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator;
33  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
34  import org.junit.Assert;
35  import org.junit.Test;
36  import org.orekit.KeyValueFileParser;
37  import org.orekit.Utils;
38  import org.orekit.attitudes.AttitudeProvider;
39  import org.orekit.bodies.CelestialBody;
40  import org.orekit.bodies.CelestialBodyFactory;
41  import org.orekit.bodies.OneAxisEllipsoid;
42  import org.orekit.errors.OrekitException;
43  import org.orekit.estimation.common.AbstractOrbitDetermination;
44  import org.orekit.estimation.common.ParameterKey;
45  import org.orekit.estimation.common.ResultKalman;
46  import org.orekit.forces.ForceModel;
47  import org.orekit.forces.drag.DragForce;
48  import org.orekit.forces.drag.DragSensitive;
49  import org.orekit.forces.drag.IsotropicDrag;
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.ICGEMFormatReader;
54  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
55  import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
56  import org.orekit.forces.radiation.RadiationSensitive;
57  import org.orekit.forces.radiation.SolarRadiationPressure;
58  import org.orekit.frames.FramesFactory;
59  import org.orekit.frames.Transform;
60  import org.orekit.models.earth.atmosphere.Atmosphere;
61  import org.orekit.models.earth.atmosphere.HarrisPriester;
62  import org.orekit.orbits.CartesianOrbit;
63  import org.orekit.orbits.Orbit;
64  import org.orekit.orbits.OrbitType;
65  import org.orekit.orbits.PositionAngle;
66  import org.orekit.propagation.SpacecraftState;
67  import org.orekit.propagation.analytical.tle.TLE;
68  import org.orekit.propagation.analytical.tle.TLEConstants;
69  import org.orekit.propagation.conversion.ODEIntegratorBuilder;
70  import org.orekit.propagation.conversion.TLEPropagatorBuilder;
71  import org.orekit.propagation.numerical.NumericalPropagator;
72  import org.orekit.time.AbsoluteDate;
73  import org.orekit.time.TimeScalesFactory;
74  import org.orekit.utils.Constants;
75  import org.orekit.utils.IERSConventions;
76  import org.orekit.utils.PVCoordinates;
77  import org.orekit.utils.ParameterDriver;
78  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
79  import org.orekit.utils.TimeStampedPVCoordinates;
80  
81  public class TLEKalmanOrbitDeterminationTest extends AbstractOrbitDetermination<TLEPropagatorBuilder> {
82  
83      /** Initial TLE. */
84      public TLE templateTLE;
85  
86      /** {@inheritDoc} */
87      @Override
88      protected void createGravityField(final KeyValueFileParser<ParameterKey> parser)
89          throws NoSuchElementException {
90          
91          // TLE OD does not need gravity field
92      }
93  
94      /** {@inheritDoc} */
95      @Override
96      protected double getMu() {
97          return TLEConstants.MU;
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     protected TLEPropagatorBuilder createPropagatorBuilder(final Orbit referenceOrbit,
103                                                             final ODEIntegratorBuilder builder,
104                                                             final double positionScale) {
105         return new TLEPropagatorBuilder(templateTLE, PositionAngle.MEAN,
106                                          positionScale);
107     }
108 
109     /** {@inheritDoc} */
110     @Override
111     protected void setMass(final TLEPropagatorBuilder propagatorBuilder,
112                                 final double mass) {
113         
114      // TLE OD does not need to set mass
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     protected List<ParameterDriver> setGravity(final TLEPropagatorBuilder propagatorBuilder,
120                                                final OneAxisEllipsoid body) {
121         return Collections.emptyList();
122 
123     }
124 
125     /** {@inheritDoc} */
126     @Override
127     protected List<ParameterDriver> setOceanTides(final TLEPropagatorBuilder propagatorBuilder,
128                                                   final IERSConventions conventions,
129                                                   final OneAxisEllipsoid body,
130                                                   final int degree, final int order) {
131         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
132                         "Ocean tides not implemented in DSST");
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     protected List<ParameterDriver> setSolidTides(final TLEPropagatorBuilder propagatorBuilder,
138                                                   final IERSConventions conventions,
139                                                   final OneAxisEllipsoid body,
140                                                   final CelestialBody[] solidTidesBodies) {
141         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
142                                   "Solid tides not implemented in DSST");
143     }
144 
145     /** {@inheritDoc} */
146     @Override
147     protected List<ParameterDriver> setThirdBody(final TLEPropagatorBuilder propagatorBuilder,
148                                                  final CelestialBody thirdBody) {
149         return Collections.emptyList();
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     protected List<ParameterDriver> setDrag(final TLEPropagatorBuilder propagatorBuilder,
155                                             final Atmosphere atmosphere, final DragSensitive spacecraft) {
156         return Collections.emptyList();
157     }
158 
159     /** {@inheritDoc} */
160     @Override
161     protected List<ParameterDriver> setSolarRadiationPressure(final TLEPropagatorBuilder propagatorBuilder, final CelestialBody sun,
162                                                               final double equatorialRadius, final RadiationSensitive spacecraft) {
163         return Collections.emptyList();
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     protected List<ParameterDriver> setAlbedoInfrared(final TLEPropagatorBuilder propagatorBuilder,
169                                                       final CelestialBody sun, final double equatorialRadius,
170                                                       final double angularResolution,
171                                                       final RadiationSensitive spacecraft) {
172         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
173                         "Albedo and infrared not implemented in TLE Propagator");
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     protected List<ParameterDriver> setRelativity(final TLEPropagatorBuilder propagatorBuilder) {
179         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
180                         "Relativity not implemented in TLE Propagator");
181     }
182 
183     /** {@inheritDoc} */
184     @Override
185     protected List<ParameterDriver> setPolynomialAcceleration(final TLEPropagatorBuilder propagatorBuilder,
186                                                               final String name, final Vector3D direction, final int degree) {
187         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
188                         "Polynomial acceleration not implemented in TLE Propagator");
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     protected void setAttitudeProvider(final TLEPropagatorBuilder propagatorBuilder,
194                                        final AttitudeProvider attitudeProvider) {
195         propagatorBuilder.setAttitudeProvider(attitudeProvider);
196     }
197 
198     @Test
199     // Orbit determination for Lageos2 based on SLR (range) measurements
200     public void testLageos2() throws URISyntaxException, IOException {
201 
202         // Print results on console
203         final boolean print = false;
204         
205         // input in resources directory
206         final String inputPath = KalmanNumericalOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/Lageos2/tle_od_test_Lageos2.in").toURI().getPath();
207         final File input  = new File(inputPath);
208 
209         // configure Orekit data acces
210         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
211         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
212         
213         // initiate TLE
214         final String line1 = "1 22195U 92070B   16045.51027931 -.00000009  00000-0  00000+0 0  9990";
215         final String line2 = "2 22195  52.6508 132.9147 0137738 336.2706   1.6348  6.47294052551192";
216         templateTLE = new TLE(line1, line2);
217         templateTLE.getParametersDrivers().get(0).setSelected(false);
218         
219         // Default for test is Cartesian
220         final OrbitType orbitType = OrbitType.CARTESIAN;
221         
222         // Initial orbital Cartesian covariance matrix
223         final RealMatrix cartesianOrbitalP = MatrixUtils.createRealDiagonalMatrix(new double [] {
224             1e4, 4e3, 1, 5e-3, 6e-5, 1e-4
225         });
226         
227         // Orbital Cartesian process noise matrix (Q)
228         final RealMatrix cartesianOrbitalQ = MatrixUtils.createRealDiagonalMatrix(new double [] {
229             1.e-4, 1.e-4, 1.e-4, 1.e-10, 1.e-10, 1.e-10
230         });
231         
232         // Initial measurement covariance matrix and process noise matrix
233         final RealMatrix measurementP = MatrixUtils.createRealDiagonalMatrix(new double [] {
234            1., 1., 1., 1. 
235         });
236         final RealMatrix measurementQ = MatrixUtils.createRealDiagonalMatrix(new double [] {
237             1e-6, 1e-6, 1e-6, 1e-6
238          });
239 
240         // Kalman orbit determination run.
241         ResultKalman kalmanLageos2 = runKalman(input, orbitType, print,
242                                                cartesianOrbitalP, cartesianOrbitalQ,
243                                                null, null,
244                                                measurementP, measurementQ);
245 
246         // Definition of the accuracy for the test
247         // Initial TLE error at last measurement date is 3997m
248         final double distanceAccuracy = 300.38;
249         final double velocityAccuracy = 0.148;
250 
251         // Tests
252         
253         // Number of measurements processed
254         final int numberOfMeas  = 95;
255         Assert.assertEquals(numberOfMeas, kalmanLageos2.getNumberOfMeasurements());
256 
257         //test on the estimated position and velocity
258         final TimeStampedPVCoordinates odPV = kalmanLageos2.getEstimatedPV();
259         final Vector3D estimatedPos = odPV.getPosition();
260         final Vector3D estimatedVel = odPV.getVelocity();
261 
262         // Reference position and velocity at estimation date 
263         final Vector3D refPos0 = new Vector3D(-5532131.956902, 10025696.592156, -3578940.040009);
264         final Vector3D refVel0 = new Vector3D(-3871.275109, -607.880985, 4280.972530);
265         final TimeStampedPVCoordinates refPV = createRef(odPV.getDate(), refPos0, refVel0);
266 
267         
268         final Vector3D refPos = refPV.getPosition();
269         final Vector3D refVel = refPV.getVelocity();
270         
271         // Check distances
272         final double dP = Vector3D.distance(refPos, estimatedPos);
273         final double dV = Vector3D.distance(refVel, estimatedVel);
274         Assert.assertEquals(0.0, dP, distanceAccuracy);
275         Assert.assertEquals(0.0, dV, velocityAccuracy);
276         
277         // Print orbit deltas
278         if (print) {
279             System.out.println("Test performances:");
280             System.out.format("\t%-30s\n",
281                             "ΔEstimated / Reference");
282             System.out.format(Locale.US, "\t%-10s %20.6f\n",
283                               "ΔP [m]", dP);
284             System.out.format(Locale.US, "\t%-10s %20.6f\n",
285                               "ΔV [m/s]", dV);
286         }
287 
288         // Test on measurements parameters
289         final List<DelegatingDriver> list = new ArrayList<DelegatingDriver>();
290         list.addAll(kalmanLageos2.getMeasurementsParameters().getDrivers());
291         sortParametersChanges(list);
292         final double[] stationOffSet = { 0.214786,  1.057400,  -0.54545 };
293         final double rangeBias = 0.12005;
294         Assert.assertEquals(stationOffSet[0], list.get(0).getValue(), distanceAccuracy);
295         Assert.assertEquals(stationOffSet[1], list.get(1).getValue(), distanceAccuracy);
296         Assert.assertEquals(stationOffSet[2], list.get(2).getValue(), distanceAccuracy);
297         Assert.assertEquals(rangeBias,        list.get(3).getValue(), distanceAccuracy);
298 
299         //test on statistic for the range residuals
300         final long nbRange = 95;
301         // Batch LS values
302         //final double[] RefStatRange = { -67.7496, 87.1117, 6.4482E-5, 33.6349 };
303         final double[] RefStatRange = { -70.790, 55.667, 5.873, 36.540 };
304         Assert.assertEquals(nbRange, kalmanLageos2.getRangeStat().getN());
305         Assert.assertEquals(RefStatRange[0], kalmanLageos2.getRangeStat().getMin(),               distanceAccuracy);
306         Assert.assertEquals(RefStatRange[1], kalmanLageos2.getRangeStat().getMax(),               distanceAccuracy);
307         Assert.assertEquals(RefStatRange[2], kalmanLageos2.getRangeStat().getMean(),              distanceAccuracy);
308         Assert.assertEquals(RefStatRange[3], kalmanLageos2.getRangeStat().getStandardDeviation(), distanceAccuracy);
309 
310     }
311     
312     @Test
313     // Orbit determination for GNSS satellite G07 based on SLR (range) measurements
314     public void testGNSS() throws URISyntaxException, IOException {
315 
316         // Print results on console
317         final boolean print = false;
318         
319         // input in resources directory
320         final String inputPath = KalmanNumericalOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/analytical/tle_od_test_GPS07.in").toURI().getPath();
321         final File input  = new File(inputPath);
322 
323         // configure Orekit data acces
324         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
325         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
326         
327         // initiate TLE
328         final String line1 = "1 32711U 08012A   16044.40566018 -.00000039 +00000-0 +00000-0 0  9993";
329         final String line2 = "2 32711 055.4362 301.3402 0091581 207.7162 151.8496 02.00563594058026";
330         templateTLE = new TLE(line1, line2);
331         templateTLE.getParametersDrivers().get(0).setSelected(false);
332         
333         // Default for test is Cartesian
334         final OrbitType orbitType = OrbitType.CARTESIAN;
335         
336         // Initial orbital Keplerian covariance matrix
337         final RealMatrix cartesianOrbitalP = MatrixUtils.createRealDiagonalMatrix(new double [] {
338             1e4, 4e3, 1, 5e-3, 6e-5, 1e-4
339         });
340         
341         // Orbital Cartesian process noise matrix (Q)
342         final RealMatrix cartesianOrbitalQ = MatrixUtils.createRealDiagonalMatrix(new double [] {
343             1.e-4, 1.e-4, 1.e-4, 1.e-10, 1.e-10, 1.e-10
344         });
345         
346         // Initial measurement covariance matrix and process noise matrix
347         final RealMatrix measurementP = MatrixUtils.createRealDiagonalMatrix(new double [] {
348            1., 1., 1., 1., 1., 1. 
349         });
350         final RealMatrix measurementQ = MatrixUtils.createRealDiagonalMatrix(new double [] {
351             1e-6, 1e-6, 1e-6, 1e-6, 1e-6, 1e-6
352          });
353 
354         // Kalman orbit determination run.
355         ResultKalman kalmanGNSS = runKalman(input, orbitType, print,
356                                             cartesianOrbitalP, cartesianOrbitalQ,
357                                             null, null,
358                                             measurementP, measurementQ);
359 
360         // Definition of the accuracy for the test
361         // Initial TLE error at last measurement date is 1053.6m
362         final double distanceAccuracy = 67.33;
363 
364         // Tests
365         
366         // Number of multiplexed measurements processed
367         final int numberOfMeas  = 661;
368         Assert.assertEquals(numberOfMeas, kalmanGNSS.getNumberOfMeasurements());
369 
370         //test on the estimated position
371         TimeStampedPVCoordinates odPV = kalmanGNSS.getEstimatedPV();
372         final Transform transform = FramesFactory.getTEME().getTransformTo(FramesFactory.getGCRF(), odPV.getDate());
373         odPV = transform.transformPVCoordinates(odPV);
374         final Vector3D estimatedPos = odPV.getPosition();
375 
376         // Reference position from GPS ephemeris (esa18836.sp3)
377         final Vector3D refPos = new Vector3D(2167703.453226041, 19788555.311260417, 17514805.616900872);  
378 
379         // Check distances
380         final double dP = Vector3D.distance(refPos, estimatedPos);
381         Assert.assertEquals(0.0, dP, distanceAccuracy);
382         
383         // Print orbit deltas
384         if (print) {
385             System.out.println("Test performances:");
386             System.out.format("\t%-30s\n",
387                             "ΔEstimated / Reference");
388             System.out.format(Locale.US, "\t%-10s %20.6f\n",
389                               "ΔP [m]", dP);
390         }
391 
392         //test on statistic for the range residuals
393         final long nbRange = 8211;
394         
395         final double[] RefStatRange = { -44.073, 51.349, 0.242, 8.602 };
396         Assert.assertEquals(nbRange, kalmanGNSS.getRangeStat().getN());
397         Assert.assertEquals(RefStatRange[0], kalmanGNSS.getRangeStat().getMin(),               distanceAccuracy);
398         Assert.assertEquals(RefStatRange[1], kalmanGNSS.getRangeStat().getMax(),               distanceAccuracy);
399         Assert.assertEquals(RefStatRange[2], kalmanGNSS.getRangeStat().getMean(),              distanceAccuracy);
400         Assert.assertEquals(RefStatRange[3], kalmanGNSS.getRangeStat().getStandardDeviation(), distanceAccuracy);
401 
402     }
403     
404     // Creates reference PV from reference PV with numerical propagation in TEME
405     public TimeStampedPVCoordinates createRef(final AbsoluteDate date, final Vector3D refPos0, final Vector3D refVel0) {
406         
407         // Initial orbit
408         final AbsoluteDate initDate = new AbsoluteDate(2016, 02, 14, 12, 14, 48.132, TimeScalesFactory.getUTC());
409         final CartesianOrbit initOrbit= new CartesianOrbit(new PVCoordinates(refPos0, refVel0), FramesFactory.getEME2000(), initDate, TLEConstants.MU);
410         final SpacecraftState initState = new SpacecraftState(initOrbit);
411         
412         // Numerical propagator initialization  
413         double[][] tolerance = NumericalPropagator.tolerances(0.001, initOrbit, OrbitType.CARTESIAN);
414         AdaptiveStepsizeIntegrator integrator =
415                 new DormandPrince853Integrator(0.001, 200, tolerance[0], tolerance[1]);
416         integrator.setInitialStepSize(60);
417         final NumericalPropagator propagator = new NumericalPropagator(integrator);
418         propagator.setInitialState(initState);
419         
420         // Force models
421         final int    degree                          = 20;
422         final int    order                           = 20;
423         final double spacecraftArea                  = 1.0;
424         final double spacecraftDragCoefficient       = 2.0;
425         final double spacecraftReflectionCoefficient = 2.0;
426         
427         // Earth gravity field
428         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
429                                                             Constants.WGS84_EARTH_FLATTENING,
430                                                             FramesFactory.getITRF(IERSConventions.IERS_2010, true));
431         final NormalizedSphericalHarmonicsProvider harmonicsGravityProvider = GravityFieldFactory.getNormalizedProvider(degree, order);
432         propagator.addForceModel(new HolmesFeatherstoneAttractionModel(earth.getBodyFrame(), harmonicsGravityProvider));
433 
434         // Sun and Moon attraction
435         propagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getSun()));
436         propagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getMoon()));
437 
438         // Atmospheric Drag
439         ForceModel drag = new DragForce(new HarrisPriester(CelestialBodyFactory.getSun(), earth),
440                                         new IsotropicDrag(spacecraftArea, spacecraftDragCoefficient));
441         propagator.addForceModel(drag);
442 
443         // Solar radiation pressure
444         propagator.addForceModel(new SolarRadiationPressure(CelestialBodyFactory.getSun(),
445                                                     earth.getEquatorialRadius(),
446                                                     new IsotropicRadiationSingleCoefficient(spacecraftArea, spacecraftReflectionCoefficient)));
447 
448         // Propagation
449         TimeStampedPVCoordinates endPV = propagator.propagate(date).getPVCoordinates();
450         final Transform transform = FramesFactory.getEME2000().getTransformTo(FramesFactory.getTEME(), date);
451         endPV = transform.transformPVCoordinates(endPV);
452         return endPV;
453     }
454 }