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.leastsquares;
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.List;
24  import java.util.NoSuchElementException;
25  
26  import org.hamcrest.MatcherAssert;
27  import org.hamcrest.Matchers;
28  import org.hipparchus.exception.LocalizedCoreFormats;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.util.FastMath;
31  import org.junit.jupiter.api.Assertions;
32  import org.junit.jupiter.api.Test;
33  import org.orekit.KeyValueFileParser;
34  import org.orekit.Utils;
35  import org.orekit.attitudes.AttitudeProvider;
36  import org.orekit.bodies.CelestialBody;
37  import org.orekit.bodies.OneAxisEllipsoid;
38  import org.orekit.errors.OrekitException;
39  import org.orekit.estimation.common.AbstractOrbitDetermination;
40  import org.orekit.estimation.common.ParameterKey;
41  import org.orekit.estimation.common.ResultBatchLeastSquares;
42  import org.orekit.forces.drag.DragSensitive;
43  import org.orekit.forces.gravity.potential.GravityFieldFactory;
44  import org.orekit.forces.gravity.potential.ICGEMFormatReader;
45  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
46  import org.orekit.forces.radiation.RadiationSensitive;
47  import org.orekit.models.earth.atmosphere.Atmosphere;
48  import org.orekit.orbits.EquinoctialOrbit;
49  import org.orekit.orbits.Orbit;
50  import org.orekit.orbits.OrbitType;
51  import org.orekit.propagation.PropagationType;
52  import org.orekit.propagation.conversion.DSSTPropagatorBuilder;
53  import org.orekit.propagation.conversion.ODEIntegratorBuilder;
54  import org.orekit.propagation.semianalytical.dsst.forces.DSSTAtmosphericDrag;
55  import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
56  import org.orekit.propagation.semianalytical.dsst.forces.DSSTSolarRadiationPressure;
57  import org.orekit.propagation.semianalytical.dsst.forces.DSSTTesseral;
58  import org.orekit.propagation.semianalytical.dsst.forces.DSSTThirdBody;
59  import org.orekit.propagation.semianalytical.dsst.forces.DSSTZonal;
60  import org.orekit.utils.Constants;
61  import org.orekit.utils.IERSConventions;
62  import org.orekit.utils.ParameterDriver;
63  
64  public class DSSTOrbitDeterminationTest extends AbstractOrbitDetermination<DSSTPropagatorBuilder> {
65  
66      /** Gravity field. */
67      private UnnormalizedSphericalHarmonicsProvider gravityField;
68  
69      /** Propagation type (mean or mean + osculating). */
70      private PropagationType propagationType;
71  
72      /** Initial state type (defined using mean or osculating elements). */
73      private PropagationType stateType;
74  
75      /** {@inheritDoc} */
76      @Override
77      protected void createGravityField(final KeyValueFileParser<ParameterKey> parser)
78          throws NoSuchElementException {
79  
80          final int degree = parser.getInt(ParameterKey.CENTRAL_BODY_DEGREE);
81          final int order  = FastMath.min(degree, parser.getInt(ParameterKey.CENTRAL_BODY_ORDER));
82          gravityField = GravityFieldFactory.getUnnormalizedProvider(degree, order);
83      }
84  
85      /** {@inheritDoc} */
86      @Override
87      protected double getMu() {
88          return gravityField.getMu();
89      }
90  
91      /** {@inheritDoc} */
92      @Override
93      protected DSSTPropagatorBuilder createPropagatorBuilder(final Orbit referenceOrbit,
94                                                              final ODEIntegratorBuilder builder,
95                                                              final double positionScale) {
96          final EquinoctialOrbit equiOrbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(referenceOrbit);
97          return new DSSTPropagatorBuilder(equiOrbit, builder, positionScale, propagationType, stateType);
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     protected void setMass(final DSSTPropagatorBuilder propagatorBuilder,
103                                 final double mass) {
104         propagatorBuilder.setMass(mass);
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     protected List<ParameterDriver> setGravity(final DSSTPropagatorBuilder propagatorBuilder,
110                                                final OneAxisEllipsoid body) {
111 
112         // tesseral terms
113         final DSSTForceModel tesseral = new DSSTTesseral(body.getBodyFrame(),
114                                                          Constants.WGS84_EARTH_ANGULAR_VELOCITY, gravityField,
115                                                          gravityField.getMaxDegree(), gravityField.getMaxOrder(), 4, 12,
116                                                          gravityField.getMaxDegree(), gravityField.getMaxOrder(), 4);
117         propagatorBuilder.addForceModel(tesseral);
118 
119         // zonal terms
120         final DSSTForceModel zonal = new DSSTZonal(gravityField, gravityField.getMaxDegree(), 4,
121                                                    2 * gravityField.getMaxDegree() + 1);
122         propagatorBuilder.addForceModel(zonal);
123 
124         // gather all drivers
125         final List<ParameterDriver> drivers = new ArrayList<>();
126         drivers.addAll(tesseral.getParametersDrivers());
127         drivers.addAll(zonal.getParametersDrivers());
128         return drivers;
129 
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     protected List<ParameterDriver> setOceanTides(final DSSTPropagatorBuilder propagatorBuilder,
135                                                   final IERSConventions conventions,
136                                                   final OneAxisEllipsoid body,
137                                                   final int degree, final int order) {
138         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
139                         "Ocean tides not implemented in DSST");
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     protected List<ParameterDriver> setSolidTides(final DSSTPropagatorBuilder propagatorBuilder,
145                                                   final IERSConventions conventions,
146                                                   final OneAxisEllipsoid body,
147                                                   final CelestialBody[] solidTidesBodies) {
148         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
149                                   "Solid tides not implemented in DSST");
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     protected List<ParameterDriver> setThirdBody(final DSSTPropagatorBuilder propagatorBuilder,
155                                                  final CelestialBody thirdBody) {
156         final DSSTForceModel thirdBodyModel = new DSSTThirdBody(thirdBody, gravityField.getMu());
157         propagatorBuilder.addForceModel(thirdBodyModel);
158         return thirdBodyModel.getParametersDrivers();
159     }
160 
161     /** {@inheritDoc} */
162     @Override
163     protected List<ParameterDriver> setDrag(final DSSTPropagatorBuilder propagatorBuilder,
164                                             final Atmosphere atmosphere, final DragSensitive spacecraft) {
165         final DSSTForceModel dragModel = new DSSTAtmosphericDrag(atmosphere, spacecraft, gravityField.getMu());
166         propagatorBuilder.addForceModel(dragModel);
167         return dragModel.getParametersDrivers();
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     protected List<ParameterDriver> setSolarRadiationPressure(final DSSTPropagatorBuilder propagatorBuilder, final CelestialBody sun,
173                                                               final OneAxisEllipsoid body, final RadiationSensitive spacecraft) {
174         final DSSTForceModel srpModel = new DSSTSolarRadiationPressure(sun, body, spacecraft, gravityField.getMu());
175         propagatorBuilder.addForceModel(srpModel);
176         return srpModel.getParametersDrivers();
177     }
178 
179     /** {@inheritDoc} */
180     @Override
181     protected List<ParameterDriver> setAlbedoInfrared(final DSSTPropagatorBuilder propagatorBuilder,
182                                                       final CelestialBody sun, final double equatorialRadius,
183                                                       final double angularResolution,
184                                                       final RadiationSensitive spacecraft) {
185         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
186                         "Albedo and infrared not implemented in DSST");
187     }
188 
189     /** {@inheritDoc} */
190     @Override
191     protected List<ParameterDriver> setRelativity(final DSSTPropagatorBuilder propagatorBuilder) {
192         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
193                         "Albedo and infrared not implemented in DSST");
194     }
195 
196     /** {@inheritDoc} */
197     @Override
198     protected List<ParameterDriver> setPolynomialAcceleration(final DSSTPropagatorBuilder propagatorBuilder,
199                                                               final String name, final Vector3D direction, final int degree) {
200         throw new OrekitException(LocalizedCoreFormats.SIMPLE_MESSAGE,
201                         "Polynomial acceleration not implemented in DSST");
202     }
203 
204     /** {@inheritDoc} */
205     @Override
206     protected void setAttitudeProvider(final DSSTPropagatorBuilder propagatorBuilder,
207                                        final AttitudeProvider attitudeProvider) {
208         propagatorBuilder.setAttitudeProvider(attitudeProvider);
209     }
210 
211     /** Lageos 2 orbit determination test using laser data.
212      * <p>
213      * This test uses both mean and osculating elements to perform the orbit determination.
214      * It is possible to consider only mean elements by changing propagationType and
215      * stateType keys.
216      * </p>
217      */
218     @Test
219     public void testLageos2()
220         throws URISyntaxException, IllegalArgumentException, IOException,
221                OrekitException {
222 
223         // input in resources directory
224         final String inputPath = DSSTOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/Lageos2/dsst_od_test_Lageos2.in").toURI().getPath();
225         final File input  = new File(inputPath);
226 
227         // configure Orekit data access
228         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
229         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
230 
231         // configure propagation and initial state types
232         propagationType = PropagationType.OSCULATING;
233         stateType = PropagationType.OSCULATING;
234 
235         //orbit determination run.
236         ResultBatchLeastSquares odLageos2 = runBLS(input, false);
237 
238         //test
239         //definition of the accuracy for the test
240         final double distanceAccuracy = 46.51;
241         final double velocityAccuracy = 1.126e-1;
242 
243         //test on the convergence
244         final int numberOfIte  = 5;
245         final int numberOfEval = 5;
246 
247         Assertions.assertEquals(numberOfIte, odLageos2.getNumberOfIteration());
248         Assertions.assertEquals(numberOfEval, odLageos2.getNumberOfEvaluation());
249 
250         //test on the estimated position and velocity
251         final Vector3D estimatedPos = odLageos2.getEstimatedPV().getPosition();
252         final Vector3D estimatedVel = odLageos2.getEstimatedPV().getVelocity();
253 
254         // Ref position from "lageos2_cpf_160213_5451.jax"
255         final Vector3D refPos = new Vector3D(7526994.072, -9646309.832, 1464110.239);
256         final Vector3D refVel = new Vector3D(3033.794, 1715.265, -4447.659);
257         final double dP = Vector3D.distance(refPos, estimatedPos);
258         final double dV = Vector3D.distance(refVel, estimatedVel);
259         Assertions.assertEquals(0.0, dP, distanceAccuracy);
260         Assertions.assertEquals(0.0, dV, velocityAccuracy);
261 
262         //test on statistic for the range residuals
263         final long nbRange = 95;
264         final double[] RefStatRange = { -28.3161, 58.490, 0.0, 14.857 };
265         Assertions.assertEquals(nbRange, odLageos2.getRangeStat().getN());
266         MatcherAssert.assertThat(odLageos2.getRangeStat().getMin(),
267                 Matchers.greaterThan(RefStatRange[0]));
268         Assertions.assertEquals(RefStatRange[0], odLageos2.getRangeStat().getMin(),               1.0e-3);
269         Assertions.assertEquals(RefStatRange[1], odLageos2.getRangeStat().getMax(),               1.0e-3);
270         Assertions.assertEquals(RefStatRange[2], odLageos2.getRangeStat().getMean(),              1.0e-3);
271         Assertions.assertEquals(RefStatRange[3], odLageos2.getRangeStat().getStandardDeviation(), 1.0e-3);
272     }
273 
274     /**
275      * GNSS orbit determination test.
276      * This test uses both mean and osculating elements to perform the orbit determination.
277      * It is possible to consider only mean elements by changing propagationType and
278      * stateType keys.
279      * Using only mean elements, results are:
280      *    ΔP = 59 meters
281      *    ΔV = 0.23 meters per second
282      *    nb iterations  = 2
283      *    nb evaluations = 3
284      *    min residual  = -83.945 meters
285      *    max residual  = 59.365 meters
286      *    mean residual = 0.23 meters
287      *    RMS = 20.857 meters
288      */
289     @Test
290     public void testGNSS()
291         throws URISyntaxException, IllegalArgumentException, IOException, OrekitException {
292 
293         // input in resources directory
294         final String inputPath = DSSTOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/GNSS/dsst_od_test_GPS07.in").toURI().getPath();
295         final File input  = new File(inputPath);
296 
297         // configure Orekit data access
298         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
299         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
300 
301         // configure propagation and initial state types
302         propagationType = PropagationType.OSCULATING;
303         stateType = PropagationType.OSCULATING;
304 
305         //orbit determination run.
306         ResultBatchLeastSquares odGNSS = runBLS(input, false);
307 
308         //test
309         //definition of the accuracy for the test
310         final double distanceAccuracy = 6.052;
311         final double velocityAccuracy = 2.48e-3;
312 
313         //test on the convergence
314         final int numberOfIte  = 3;
315         final int numberOfEval = 4;
316 
317         Assertions.assertEquals(numberOfIte, odGNSS.getNumberOfIteration());
318         Assertions.assertEquals(numberOfEval, odGNSS.getNumberOfEvaluation());
319 
320         //test on the estimated position and velocity (reference from IGS-MGEX file com18836.sp3)
321         final Vector3D estimatedPos = odGNSS.getEstimatedPV().getPosition();
322         final Vector3D estimatedVel = odGNSS.getEstimatedPV().getVelocity();
323         final Vector3D refPos = new Vector3D(-2747606.680868164, 22572091.30648564, 13522761.402325712);
324         final Vector3D refVel = new Vector3D(-2729.5151218788005, 1142.6629459030657, -2523.9055974487947);
325         Assertions.assertEquals(0.0, Vector3D.distance(refPos, estimatedPos), distanceAccuracy);
326         Assertions.assertEquals(0.0, Vector3D.distance(refVel, estimatedVel), velocityAccuracy);
327 
328         //test on statistic for the range residuals
329         final long nbRange = 4009;
330         final double[] RefStatRange = { -3.480, 2.608, 0.0, 0.836 };
331         Assertions.assertEquals(nbRange, odGNSS.getRangeStat().getN());
332         Assertions.assertEquals(RefStatRange[0], odGNSS.getRangeStat().getMin(),               1.0e-3);
333         Assertions.assertEquals(RefStatRange[1], odGNSS.getRangeStat().getMax(),               1.0e-3);
334         Assertions.assertEquals(RefStatRange[2], odGNSS.getRangeStat().getMean(),              1.0e-3);
335         Assertions.assertEquals(RefStatRange[3], odGNSS.getRangeStat().getStandardDeviation(), 1.0e-3);
336     }
337 }