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