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