1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.estimation.sequential;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.net.URISyntaxException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Locale;
27
28 import org.hipparchus.geometry.euclidean.threed.Vector3D;
29 import org.hipparchus.linear.MatrixUtils;
30 import org.hipparchus.linear.RealMatrix;
31 import org.hipparchus.linear.RealVector;
32 import org.hipparchus.stat.descriptive.StreamingStatistics;
33 import org.hipparchus.util.FastMath;
34 import org.hipparchus.util.MerweUnscentedTransform;
35 import org.junit.jupiter.api.Assertions;
36 import org.junit.jupiter.api.Test;
37 import org.orekit.Utils;
38 import org.orekit.attitudes.FrameAlignedProvider;
39 import org.orekit.bodies.CelestialBody;
40 import org.orekit.bodies.CelestialBodyFactory;
41 import org.orekit.bodies.OneAxisEllipsoid;
42 import org.orekit.data.DataFilter;
43 import org.orekit.data.DataSource;
44 import org.orekit.data.GzipFilter;
45 import org.orekit.data.UnixCompressFilter;
46 import org.orekit.estimation.measurements.EstimatedMeasurement;
47 import org.orekit.estimation.measurements.ObservableSatellite;
48 import org.orekit.estimation.measurements.ObservedMeasurement;
49 import org.orekit.estimation.measurements.Position;
50 import org.orekit.files.ilrs.CPF;
51 import org.orekit.files.ilrs.CPF.CPFCoordinate;
52 import org.orekit.files.ilrs.CPF.CPFEphemeris;
53 import org.orekit.files.rinex.HatanakaCompressFilter;
54 import org.orekit.files.ilrs.CPFParser;
55 import org.orekit.forces.ForceModel;
56 import org.orekit.forces.drag.DragForce;
57 import org.orekit.forces.drag.DragSensitive;
58 import org.orekit.forces.drag.IsotropicDrag;
59 import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
60 import org.orekit.forces.gravity.NewtonianAttraction;
61 import org.orekit.forces.gravity.SolidTides;
62 import org.orekit.forces.gravity.ThirdBodyAttraction;
63 import org.orekit.forces.gravity.potential.GravityFieldFactory;
64 import org.orekit.forces.gravity.potential.ICGEMFormatReader;
65 import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
66 import org.orekit.forces.gravity.potential.SphericalHarmonicsProvider;
67 import org.orekit.forces.radiation.IsotropicRadiationSingleCoefficient;
68 import org.orekit.forces.radiation.RadiationSensitive;
69 import org.orekit.forces.radiation.SolarRadiationPressure;
70 import org.orekit.frames.Frame;
71 import org.orekit.frames.FramesFactory;
72 import org.orekit.models.earth.atmosphere.Atmosphere;
73 import org.orekit.models.earth.atmosphere.NRLMSISE00;
74 import org.orekit.models.earth.atmosphere.data.MarshallSolarActivityFutureEstimation;
75 import org.orekit.orbits.CartesianOrbit;
76 import org.orekit.orbits.Orbit;
77 import org.orekit.orbits.PositionAngleType;
78 import org.orekit.propagation.BoundedPropagator;
79 import org.orekit.propagation.conversion.DormandPrince853IntegratorBuilder;
80 import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
81 import org.orekit.propagation.conversion.ODEIntegratorBuilder;
82 import org.orekit.propagation.conversion.PropagatorBuilder;
83 import org.orekit.time.AbsoluteDate;
84 import org.orekit.time.TimeScalesFactory;
85 import org.orekit.utils.Constants;
86 import org.orekit.utils.IERSConventions;
87 import org.orekit.utils.ParameterDriver;
88 import org.orekit.utils.TimeStampedPVCoordinates;
89
90 public class UnscentedKalmanOrbitDeterminationTest {
91
92
93 private static final String HEADER = "%-25s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s\t%16s";
94
95
96 private static final String DATA_LINE = "%-25s\t%-16.6f\t%-16.6f\t%16.9f\t%-16.6f\t%-16.6f\t%16.9f\t%-16.6f\t%-16.6f\t%16.9f\t%16.9f\t%16.9f\t%16.9f\t%16.9f\t%16.9f\t%16.9f\t%16.9f\t%16.9f";
97
98
99 private static boolean print;
100
101
102
103
104
105
106
107
108 @Test
109 public void testLageos() throws URISyntaxException, IOException {
110
111
112 print = false;
113
114
115 Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
116 GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
117
118
119 final CPFEphemeris observations = initializeObservations("orbit-determination/Lageos2/lageos2_cpf_160213_5441.sgf");
120
121
122 final IERSConventions convention = IERSConventions.IERS_2010;
123 final boolean simpleEop = true;
124 final OneAxisEllipsoid centralBody = initializeBody(convention, simpleEop);
125
126
127 final int degree = 16;
128 final int order = 16;
129 final SphericalHarmonicsProvider gravityField = initializeGravityField(degree, order);
130
131
132 final Orbit initialOrbit = initializeOrbit(observations, gravityField);
133
134
135 final double minStep = 0.001;
136 final double maxStep = 300.0;
137 final double surface = 0.2831331;
138 final double mass = 405.38;
139 final boolean useDrag = false;
140 final boolean useSrp = true;
141 final boolean useMoon = true;
142 final boolean useSun = true;
143 final boolean useTides = true;
144 final PropagatorBuilder propagator = initializePropagator(initialOrbit, centralBody, gravityField,
145 convention, simpleEop, minStep, maxStep,
146 mass, surface, useDrag, useSrp,
147 useSun, useMoon, useTides);
148
149
150 final double sigma = 1.0;
151 final List<ObservedMeasurement<?>> measurements = initializeMeasurements(observations, initialOrbit, sigma);
152
153
154 final RealMatrix orbitalP = MatrixUtils.createRealDiagonalMatrix(new double[] {
155 100.0, 100.0, 100.0, 1.0e-3, 1.0e-3, 1.0e-3
156 });
157
158
159 final RealMatrix orbitalQ = MatrixUtils.createRealDiagonalMatrix(new double[] {1.0e-6, 1.0e-6, 1.0e-6, 1.0e-9, 1.0e-9, 1.0e-9});
160 final CovarianceMatrixProvider provider = buildCovarianceProvider(orbitalP, orbitalQ);
161
162
163 final Observer observer = initializeEstimator(propagator, measurements, provider);
164
165
166 final KalmanEstimation estimation = observer.getEstimation();
167 final StreamingStatistics statX = observer.getXStatistics();
168 final StreamingStatistics statY = observer.getYStatistics();
169 final StreamingStatistics statZ = observer.getZStatistics();
170 Assertions.assertEquals(0.0, statX.getMean(), 1.39e-3);
171 Assertions.assertEquals(0.0, statY.getMean(), 1.86e-4);
172 Assertions.assertEquals(0.0, statZ.getMean(), 2.85e-4);
173 Assertions.assertEquals(0.0, statX.getMin(), 0.031);
174 Assertions.assertEquals(0.0, statY.getMin(), 0.028);
175 Assertions.assertEquals(0.0, statZ.getMin(), 0.029);
176 Assertions.assertEquals(0.0, statX.getMax(), 0.026);
177 Assertions.assertEquals(0.0, statY.getMax(), 0.032);
178 Assertions.assertEquals(0.0, statZ.getMax(), 0.027);
179
180
181 final RealVector estimatedState = estimation.getPhysicalEstimatedState();
182 final Vector3D ref = ((Position) measurements.get(measurements.size() - 1)).getPosition();
183 final Vector3D estimated = new Vector3D(estimatedState.getEntry(0),
184 estimatedState.getEntry(1),
185 estimatedState.getEntry(2));
186 final double dP = 0.029;
187 Assertions.assertEquals(0.0, Vector3D.distance(ref, estimated), dP);
188
189
190 Assertions.assertNotNull(estimation.getPhysicalInnovationCovarianceMatrix());
191 Assertions.assertNotNull(estimation.getPhysicalKalmanGain());
192
193
194 Assertions.assertNull(estimation.getPhysicalMeasurementJacobian());
195 Assertions.assertNull(estimation.getPhysicalStateTransitionMatrix());
196
197 }
198
199
200
201
202
203
204
205
206 private CPFEphemeris initializeObservations(final String fileName) throws URISyntaxException, IOException {
207
208
209 final String inputPath = UnscentedKalmanOrbitDeterminationTest.class.getClassLoader().
210 getResource(fileName).
211 toURI().
212 getPath();
213 final File file = new File(inputPath);
214
215
216 DataSource source = new DataSource(file.getName(), () -> new FileInputStream(new File(file.getParentFile(), file.getName())));
217 for (final DataFilter filter : Arrays.asList(new GzipFilter(),
218 new UnixCompressFilter(),
219 new HatanakaCompressFilter())) {
220 source = filter.filter(source);
221 }
222
223
224 final CPF cpfFile = new CPFParser().parse(source);
225 return cpfFile.getSatellites().get(cpfFile.getHeader().getIlrsSatelliteId());
226
227 }
228
229
230
231
232
233
234
235 private static OneAxisEllipsoid initializeBody(final IERSConventions convention, final boolean simpleEop) {
236
237 return new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
238 Constants.WGS84_EARTH_FLATTENING,
239 FramesFactory.getITRF(convention, simpleEop));
240 }
241
242
243
244
245
246
247
248 private static SphericalHarmonicsProvider initializeGravityField(final int degree, final int order) {
249 return GravityFieldFactory.getNormalizedProvider(degree, order);
250 }
251
252
253
254
255
256
257
258
259
260
261
262 private static Orbit initializeOrbit(final CPFEphemeris ephemeris, final SphericalHarmonicsProvider gravityField) {
263
264
265 final Frame orbitFrame = FramesFactory.getEME2000();
266
267
268 final BoundedPropagator bounded = ephemeris.getPropagator(new FrameAlignedProvider(orbitFrame));
269
270
271 final AbsoluteDate initialDate = bounded.getMinDate();
272
273
274 final TimeStampedPVCoordinates pvInitITRF = bounded.getPVCoordinates(initialDate, ephemeris.getFrame());
275 final TimeStampedPVCoordinates pvInitInertial = ephemeris.getFrame().getTransformTo(orbitFrame, initialDate).
276 transformPVCoordinates(pvInitITRF);
277
278
279 return new CartesianOrbit(new TimeStampedPVCoordinates(pvInitInertial.getDate(),
280 new Vector3D(pvInitInertial.getPosition().getX(),
281 pvInitInertial.getPosition().getY(),
282 pvInitInertial.getPosition().getZ()),
283 new Vector3D(pvInitInertial.getVelocity().getX(),
284 pvInitInertial.getVelocity().getY(),
285 pvInitInertial.getVelocity().getZ())),
286 orbitFrame, gravityField.getMu());
287
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308 private static PropagatorBuilder initializePropagator(final Orbit orbit,
309 final OneAxisEllipsoid centralBody,
310 final SphericalHarmonicsProvider gravityField,
311 final IERSConventions convention, final boolean simpleEop,
312 final double minStep, final double maxStep,
313 final double mass, final double surface,
314 final boolean useDrag, final boolean useSrp,
315 final boolean useSun, final boolean useMoon,
316 final boolean useTides) {
317
318
319 final ODEIntegratorBuilder integrator = new DormandPrince853IntegratorBuilder(minStep, maxStep, 10.0);
320
321
322 final PropagatorBuilder builder;
323
324
325 final NumericalPropagatorBuilder propagator = new NumericalPropagatorBuilder(orbit, integrator, PositionAngleType.MEAN, 10.0);
326
327
328 addNumericalForceModels(propagator, orbit, centralBody, gravityField, convention, simpleEop, surface, useDrag, useSrp, useSun, useMoon, useTides);
329
330
331 propagator.setMass(mass);
332
333
334 builder = propagator;
335
336
337 builder.resetOrbit(orbit);
338
339
340 return builder;
341
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359 private static void addNumericalForceModels(final NumericalPropagatorBuilder propagator,
360 final Orbit initialOrbit,
361 final OneAxisEllipsoid centralBody,
362 final SphericalHarmonicsProvider gravityField,
363 final IERSConventions convention, final boolean simpleEop,
364 final double surface,
365 final boolean useDrag, final boolean useSrp,
366 final boolean useSun, final boolean useMoon,
367 final boolean useTides) {
368
369
370 final List<CelestialBody> solidTidesBodies = new ArrayList<>();
371
372
373 if (useDrag) {
374
375
376 final MarshallSolarActivityFutureEstimation msafe =
377 new MarshallSolarActivityFutureEstimation(MarshallSolarActivityFutureEstimation.DEFAULT_SUPPORTED_NAMES,
378 MarshallSolarActivityFutureEstimation.StrengthLevel.AVERAGE);
379 final Atmosphere atmosphere = new NRLMSISE00(msafe, CelestialBodyFactory.getSun(), centralBody);
380
381
382
383 final ForceModel drag = new DragForce(atmosphere, new IsotropicDrag(surface, 1.0));
384 for (final ParameterDriver driver : drag.getParametersDrivers()) {
385 if (driver.getName().equals(DragSensitive.DRAG_COEFFICIENT)) {
386 driver.setSelected(true);
387 }
388 }
389
390
391 propagator.addForceModel(drag);
392
393 }
394
395
396 if (useSrp) {
397
398
399 final RadiationSensitive spacecraft = new IsotropicRadiationSingleCoefficient(surface, 1.13);
400
401
402 final ForceModel srp = new SolarRadiationPressure(CelestialBodyFactory.getSun(), centralBody, spacecraft);
403 for (final ParameterDriver driver : srp.getParametersDrivers()) {
404 if (driver.getName().equals(RadiationSensitive.REFLECTION_COEFFICIENT)) {
405
406 }
407 }
408
409
410 propagator.addForceModel(srp);
411
412 }
413
414
415 if (useSun) {
416 solidTidesBodies.add(CelestialBodyFactory.getSun());
417 propagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getSun()));
418 }
419
420
421 if (useMoon) {
422 solidTidesBodies.add(CelestialBodyFactory.getMoon());
423 propagator.addForceModel(new ThirdBodyAttraction(CelestialBodyFactory.getMoon()));
424 }
425
426
427 if (useTides) {
428 propagator.addForceModel(new SolidTides(centralBody.getBodyFrame(),
429 gravityField.getAe(), gravityField.getMu(),
430 gravityField.getTideSystem(), convention,
431 TimeScalesFactory.getUT1(convention, simpleEop),
432 solidTidesBodies.toArray(new CelestialBody[solidTidesBodies.size()])));
433 }
434
435
436 propagator.addForceModel(new HolmesFeatherstoneAttractionModel(centralBody.getBodyFrame(), (NormalizedSphericalHarmonicsProvider) gravityField));
437
438
439 propagator.addForceModel(new NewtonianAttraction(gravityField.getMu()));
440
441 }
442
443
444
445
446
447
448
449
450 private static List<ObservedMeasurement<?>> initializeMeasurements(final CPFEphemeris ephemeris,
451 final Orbit orbit,
452 final double sigma) {
453
454
455 final ObservableSatellite satellite = new ObservableSatellite(0);
456
457
458 final List<ObservedMeasurement<?>> measurements = new ArrayList<>();
459
460
461 for (final CPFCoordinate coordinate : ephemeris.getCoordinates()) {
462
463
464 final Vector3D posInertial = ephemeris.getFrame().getStaticTransformTo(orbit.getFrame(), coordinate.getDate()).
465 transformPosition(coordinate.getPosition());
466
467
468 final Position measurement = new Position(coordinate.getDate(), posInertial, sigma, 1.0, satellite);
469
470
471 measurements.add(measurement);
472
473 }
474
475
476 return measurements;
477
478 }
479
480
481
482
483
484
485
486 private static Observer initializeEstimator(final PropagatorBuilder propagator,
487 final List<ObservedMeasurement<?>> measurements,
488 final CovarianceMatrixProvider provider) {
489
490
491 final UnscentedKalmanEstimatorBuilder builder = new UnscentedKalmanEstimatorBuilder();
492
493
494 builder.addPropagationConfiguration((NumericalPropagatorBuilder) propagator, provider);
495
496
497 builder.unscentedTransformProvider(new MerweUnscentedTransform(propagator.getSelectedNormalizedParameters().length));
498
499
500 final UnscentedKalmanEstimator estimator = builder.build();
501
502 final Observer observer = new Observer();
503 estimator.setObserver(observer);
504
505
506 estimator.processMeasurements(measurements);
507
508
509 return observer;
510
511 }
512
513
514
515
516
517
518
519 private static CovarianceMatrixProvider buildCovarianceProvider(final RealMatrix initialNoiseMatrix, final RealMatrix processNoiseMatrix) {
520
521 return new ConstantProcessNoise(initialNoiseMatrix, processNoiseMatrix);
522 }
523
524
525 public static class Observer implements KalmanObserver {
526
527
528 private StreamingStatistics statX;
529
530
531 private StreamingStatistics statY;
532
533
534 private StreamingStatistics statZ;
535
536
537 private KalmanEstimation estimation;
538
539
540 public Observer() {
541 statX = new StreamingStatistics();
542 statY = new StreamingStatistics();
543 statZ = new StreamingStatistics();
544 if (print) {
545 final String header = String.format(Locale.US, HEADER, "Epoch",
546 "X Observed (m)", "X Estimated (m)", "X residual (m)",
547 "Y Observed (m)", "Y Estimated (m)", "Y residual (m)",
548 "Z Observed (m)", "Z Estimated (m)", "Z residual (m)",
549 "Cov(0;0)", "Cov(1;1)", "Cov(2;2)", "Cov(3;3)", "Cov(4;4)", "Cov(5;5)",
550 "3D Pos Cov (m)", "3D Vel Cov (m)");
551 System.out.println(header);
552 }
553
554 }
555
556
557 @Override
558 public void evaluationPerformed(final KalmanEstimation estimation) {
559
560
561 final EstimatedMeasurement<?> estimatedMeasurement = estimation.getCorrectedMeasurement();
562
563
564 if (estimatedMeasurement.getObservedMeasurement() instanceof Position) {
565
566 if (estimatedMeasurement.getStatus() == EstimatedMeasurement.Status.REJECTED) {
567 if (print) {
568 System.out.println("REJECTED");
569 }
570 } else {
571 final double[] estimated = estimatedMeasurement.getEstimatedValue();
572 final double[] observed = estimatedMeasurement.getObservedValue();
573
574
575 final double observedX = observed[0];
576 final double observedY = observed[1];
577 final double observedZ = observed[2];
578
579
580 final double estimatedX = estimated[0];
581 final double estimatedY = estimated[1];
582 final double estimatedZ = estimated[2];
583
584
585 final double resX = estimatedX - observedX;
586 final double resY = estimatedY - observedY;
587 final double resZ = estimatedZ - observedZ;
588 statX.addValue(resX);
589 statY.addValue(resY);
590 statZ.addValue(resZ);
591
592 if (print) {
593
594
595 final RealMatrix covariance = estimation.getPhysicalEstimatedCovarianceMatrix();
596 final double cov11 = covariance.getEntry(0, 0);
597 final double cov22 = covariance.getEntry(1, 1);
598 final double cov33 = covariance.getEntry(2, 2);
599 final double cov44 = covariance.getEntry(3, 3);
600 final double cov55 = covariance.getEntry(4, 4);
601 final double cov66 = covariance.getEntry(5, 5);
602 final double cPos = FastMath.sqrt(cov11 + cov22 + cov33);
603 final double cVel = FastMath.sqrt(cov44 + cov55 + cov66);
604
605
606 final String line = String.format(Locale.US, DATA_LINE, estimatedMeasurement.getDate(),
607 observedX, estimatedX, resX,
608 observedY, estimatedY, resY,
609 observedZ, estimatedZ, resZ,
610 cov11, cov22, cov33, cov44, cov55, cov66,
611 cPos, cVel);
612 System.out.println(line);
613 }
614
615 }
616
617 }
618
619 this.estimation = estimation;
620
621 }
622
623
624
625
626
627 public StreamingStatistics getXStatistics() {
628 if (print) {
629 System.out.println("Min X res (m): " + statX.getMin() + " Max X res (m): " + statX.getMax() + " Mean X res (m): " + statX.getMean() + " STD: " + statX.getStandardDeviation());
630 }
631 return statX;
632 }
633
634
635
636
637
638 public StreamingStatistics getYStatistics() {
639 if (print) {
640 System.out.println("Min Y res (m): " + statY.getMin() + " Max Y res (m): " + statY.getMax() + " Mean Y res (m): " + statY.getMean() + " STD: " + statY.getStandardDeviation());
641 }
642 return statY;
643 }
644
645
646
647
648
649 public StreamingStatistics getZStatistics() {
650 if (print) {
651 System.out.println("Min Z res (m): " + statZ.getMin() + " Max Z res (m): " + statZ.getMax() + " Mean Z res (m): " + statZ.getMean() + " STD: " + statZ.getStandardDeviation());
652 }
653 return statZ;
654 }
655
656
657
658
659
660 public KalmanEstimation getEstimation() {
661 return estimation;
662 }
663
664 }
665
666 }