1 /* Copyright 2002-2019 CS Systèmes d'Information
2 * Licensed to CS Systèmes d'Information (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.propagation;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.stream.Stream;
26
27 import org.hipparchus.analysis.interpolation.HermiteInterpolator;
28 import org.hipparchus.exception.LocalizedCoreFormats;
29 import org.hipparchus.exception.MathIllegalStateException;
30 import org.hipparchus.geometry.euclidean.threed.Rotation;
31 import org.hipparchus.geometry.euclidean.threed.Vector3D;
32 import org.hipparchus.util.FastMath;
33 import org.orekit.attitudes.Attitude;
34 import org.orekit.attitudes.LofOffset;
35 import org.orekit.errors.OrekitException;
36 import org.orekit.errors.OrekitIllegalArgumentException;
37 import org.orekit.errors.OrekitMessages;
38 import org.orekit.frames.Frame;
39 import org.orekit.frames.LOFType;
40 import org.orekit.frames.Transform;
41 import org.orekit.orbits.Orbit;
42 import org.orekit.time.AbsoluteDate;
43 import org.orekit.time.TimeInterpolable;
44 import org.orekit.time.TimeShiftable;
45 import org.orekit.time.TimeStamped;
46 import org.orekit.utils.TimeStampedAngularCoordinates;
47 import org.orekit.utils.TimeStampedPVCoordinates;
48
49
50 /** This class is the representation of a complete state holding orbit, attitude
51 * and mass information at a given date.
52 *
53 * <p>It contains an {@link Orbit orbital state} at a current
54 * {@link AbsoluteDate} both handled by an {@link Orbit}, plus the current
55 * mass and attitude. Orbit and state are guaranteed to be consistent in terms
56 * of date and reference frame. The spacecraft state may also contain additional
57 * states, which are simply named double arrays which can hold any user-defined
58 * data.
59 * </p>
60 * <p>
61 * The state can be slightly shifted to close dates. This shift is based on
62 * a simple Keplerian model for orbit, a linear extrapolation for attitude
63 * taking the spin rate into account and no mass change. It is <em>not</em>
64 * intended as a replacement for proper orbit and attitude propagation but
65 * should be sufficient for either small time shifts or coarse accuracy.
66 * </p>
67 * <p>
68 * The instance <code>SpacecraftState</code> is guaranteed to be immutable.
69 * </p>
70 * @see org.orekit.propagation.numerical.NumericalPropagator
71 * @author Fabien Maussion
72 * @author Véronique Pommier-Maurussane
73 * @author Luc Maisonobe
74 */
75 public class SpacecraftState
76 implements TimeStamped, TimeShiftable<SpacecraftState>, TimeInterpolable<SpacecraftState>, Serializable {
77
78 /** Serializable UID. */
79 private static final long serialVersionUID = 20130407L;
80
81 /** Default mass. */
82 private static final double DEFAULT_MASS = 1000.0;
83
84 /**
85 * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns
86 * corresponds to sub-mm accuracy at LEO orbital velocities.
87 */
88 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
89
90 /** Orbital state. */
91 private final Orbit orbit;
92
93 /** Attitude. */
94 private final Attitude attitude;
95
96 /** Current mass (kg). */
97 private final double mass;
98
99 /** Additional states. */
100 private final Map<String, double[]> additional;
101
102 /** Build a spacecraft state from orbit only.
103 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
104 * @param orbit the orbit
105 */
106 public SpacecraftState(final Orbit orbit) {
107 this(orbit,
108 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
109 DEFAULT_MASS, null);
110 }
111
112 /** Build a spacecraft state from orbit and attitude provider.
113 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
114 * @param orbit the orbit
115 * @param attitude attitude
116 * @exception IllegalArgumentException if orbit and attitude dates
117 * or frames are not equal
118 */
119 public SpacecraftState(final Orbit orbit, final Attitude attitude)
120 throws IllegalArgumentException {
121 this(orbit, attitude, DEFAULT_MASS, null);
122 }
123
124 /** Create a new instance from orbit and mass.
125 * <p>Attitude law is set to an unspecified default attitude.</p>
126 * @param orbit the orbit
127 * @param mass the mass (kg)
128 */
129 public SpacecraftState(final Orbit orbit, final double mass) {
130 this(orbit,
131 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
132 mass, null);
133 }
134
135 /** Build a spacecraft state from orbit, attitude provider and mass.
136 * @param orbit the orbit
137 * @param attitude attitude
138 * @param mass the mass (kg)
139 * @exception IllegalArgumentException if orbit and attitude dates
140 * or frames are not equal
141 */
142 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass)
143 throws IllegalArgumentException {
144 this(orbit, attitude, mass, null);
145 }
146
147 /** Build a spacecraft state from orbit only.
148 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
149 * @param orbit the orbit
150 * @param additional additional states
151 */
152 public SpacecraftState(final Orbit orbit, final Map<String, double[]> additional) {
153 this(orbit,
154 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
155 DEFAULT_MASS, additional);
156 }
157
158 /** Build a spacecraft state from orbit and attitude provider.
159 * <p>Mass is set to an unspecified non-null arbitrary value.</p>
160 * @param orbit the orbit
161 * @param attitude attitude
162 * @param additional additional states
163 * @exception IllegalArgumentException if orbit and attitude dates
164 * or frames are not equal
165 */
166 public SpacecraftState(final Orbit orbit, final Attitude attitude, final Map<String, double[]> additional)
167 throws IllegalArgumentException {
168 this(orbit, attitude, DEFAULT_MASS, additional);
169 }
170
171 /** Create a new instance from orbit and mass.
172 * <p>Attitude law is set to an unspecified default attitude.</p>
173 * @param orbit the orbit
174 * @param mass the mass (kg)
175 * @param additional additional states
176 */
177 public SpacecraftState(final Orbit orbit, final double mass, final Map<String, double[]> additional) {
178 this(orbit,
179 new LofOffset(orbit.getFrame(), LOFType.VVLH).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
180 mass, additional);
181 }
182
183 /** Build a spacecraft state from orbit, attitude provider and mass.
184 * @param orbit the orbit
185 * @param attitude attitude
186 * @param mass the mass (kg)
187 * @param additional additional states (may be null if no additional states are available)
188 * @exception IllegalArgumentException if orbit and attitude dates
189 * or frames are not equal
190 */
191 public SpacecraftState(final Orbit orbit, final Attitude attitude,
192 final double mass, final Map<String, double[]> additional)
193 throws IllegalArgumentException {
194 checkConsistency(orbit, attitude);
195 this.orbit = orbit;
196 this.attitude = attitude;
197 this.mass = mass;
198 if (additional == null) {
199 this.additional = Collections.emptyMap();
200 } else {
201 this.additional = new HashMap<String, double[]>(additional.size());
202 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
203 this.additional.put(entry.getKey(), entry.getValue().clone());
204 }
205 }
206 }
207
208 /** Add an additional state.
209 * <p>
210 * {@link SpacecraftState SpacecraftState} instances are immutable,
211 * so this method does <em>not</em> change the instance, but rather
212 * creates a new instance, which has the same orbit, attitude, mass
213 * and additional states as the original instance, except it also
214 * has the specified state. If the original instance already had an
215 * additional state with the same name, it will be overridden. If it
216 * did not have any additional state with that name, the new instance
217 * will have one more additional state than the original instance.
218 * </p>
219 * @param name name of the additional state
220 * @param value value of the additional state
221 * @return a new instance, with the additional state added
222 * @see #hasAdditionalState(String)
223 * @see #getAdditionalState(String)
224 * @see #getAdditionalStates()
225 */
226 public SpacecraftState addAdditionalState(final String name, final double... value) {
227 final Map<String, double[]> newMap = new HashMap<String, double[]>(additional.size() + 1);
228 newMap.putAll(additional);
229 newMap.put(name, value.clone());
230 return new SpacecraftState(orbit, attitude, mass, newMap);
231 }
232
233 /** Check orbit and attitude dates are equal.
234 * @param orbit the orbit
235 * @param attitude attitude
236 * @exception IllegalArgumentException if orbit and attitude dates
237 * are not equal
238 */
239 private static void checkConsistency(final Orbit orbit, final Attitude attitude)
240 throws IllegalArgumentException {
241 if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) >
242 DATE_INCONSISTENCY_THRESHOLD) {
243 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
244 orbit.getDate(), attitude.getDate());
245 }
246 if (orbit.getFrame() != attitude.getReferenceFrame()) {
247 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
248 orbit.getFrame().getName(),
249 attitude.getReferenceFrame().getName());
250 }
251 }
252
253 /** Get a time-shifted state.
254 * <p>
255 * The state can be slightly shifted to close dates. This shift is based on
256 * simple models. For orbits, the model is a Keplerian one if no derivatives
257 * are available in the orbit, or Keplerian plus quadratic effect of the
258 * non-Keplerian acceleration if derivatives are available. For attitude,
259 * a polynomial model is used. Neither mass nor additional states change.
260 * Shifting is <em>not</em> intended as a replacement for proper orbit
261 * and attitude propagation but should be sufficient for small time shifts
262 * or coarse accuracy.
263 * </p>
264 * <p>
265 * As a rough order of magnitude, the following table shows the extrapolation
266 * errors obtained between this simple shift method and an {@link
267 * org.orekit.propagation.numerical.NumericalPropagator numerical
268 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
269 * Sun and Moon third bodies attractions, drag and solar radiation pressure.
270 * Beware that these results will be different for other orbits.
271 * </p>
272 * <table border="1" cellpadding="5">
273 * <caption>Extrapolation Error</caption>
274 * <tr bgcolor="#ccccff"><th>interpolation time (s)</th>
275 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
276 * <tr><td bgcolor="#eeeeff"> 60</td><td> 18</td><td> 1.1</td></tr>
277 * <tr><td bgcolor="#eeeeff">120</td><td> 72</td><td> 9.1</td></tr>
278 * <tr><td bgcolor="#eeeeff">300</td><td> 447</td><td> 140</td></tr>
279 * <tr><td bgcolor="#eeeeff">600</td><td>1601</td><td>1067</td></tr>
280 * <tr><td bgcolor="#eeeeff">900</td><td>3141</td><td>3307</td></tr>
281 * </table>
282 * @param dt time shift in seconds
283 * @return a new state, shifted with respect to the instance (which is immutable)
284 * except for the mass and additional states which stay unchanged
285 */
286 public SpacecraftState shiftedBy(final double dt) {
287 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
288 mass, additional);
289 }
290
291 /** {@inheritDoc}
292 * <p>
293 * The additional states that are interpolated are the ones already present
294 * in the instance. The sample instances must therefore have at least the same
295 * additional states has the instance. They may have more additional states,
296 * but the extra ones will be ignored.
297 * </p>
298 * <p>
299 * As this implementation of interpolation is polynomial, it should be used only
300 * with small samples (about 10-20 points) in order to avoid <a
301 * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
302 * and numerical problems (including NaN appearing).
303 * </p>
304 */
305 public SpacecraftState interpolate(final AbsoluteDate date,
306 final Stream<SpacecraftState> sample) {
307
308 // prepare interpolators
309 final List<Orbit> orbits = new ArrayList<>();
310 final List<Attitude> attitudes = new ArrayList<>();
311 final HermiteInterpolator massInterpolator = new HermiteInterpolator();
312 final Map<String, HermiteInterpolator> additionalInterpolators =
313 new HashMap<String, HermiteInterpolator>(additional.size());
314 for (final String name : additional.keySet()) {
315 additionalInterpolators.put(name, new HermiteInterpolator());
316 }
317
318 // extract sample data
319 sample.forEach(state -> {
320 final double deltaT = state.getDate().durationFrom(date);
321 orbits.add(state.getOrbit());
322 attitudes.add(state.getAttitude());
323 massInterpolator.addSamplePoint(deltaT,
324 new double[] {
325 state.getMass()
326 });
327 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
328 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
329 }
330 });
331
332 // perform interpolations
333 final Orbit interpolatedOrbit = orbit.interpolate(date, orbits);
334 final Attitude interpolatedAttitude = attitude.interpolate(date, attitudes);
335 final double interpolatedMass = massInterpolator.value(0)[0];
336 final Map<String, double[]> interpolatedAdditional;
337 if (additional.isEmpty()) {
338 interpolatedAdditional = null;
339 } else {
340 interpolatedAdditional = new HashMap<String, double[]>(additional.size());
341 for (final Map.Entry<String, HermiteInterpolator> entry : additionalInterpolators.entrySet()) {
342 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(0));
343 }
344 }
345
346 // create the complete interpolated state
347 return new SpacecraftState(interpolatedOrbit, interpolatedAttitude,
348 interpolatedMass, interpolatedAdditional);
349
350 }
351
352 /** Gets the current orbit.
353 * @return the orbit
354 */
355 public Orbit getOrbit() {
356 return orbit;
357 }
358
359 /** Get the date.
360 * @return date
361 */
362 public AbsoluteDate getDate() {
363 return orbit.getDate();
364 }
365
366 /** Get the inertial frame.
367 * @return the frame
368 */
369 public Frame getFrame() {
370 return orbit.getFrame();
371 }
372
373 /** Check if an additional state is available.
374 * @param name name of the additional state
375 * @return true if the additional state is available
376 * @see #addAdditionalState(String, double[])
377 * @see #getAdditionalState(String)
378 * @see #getAdditionalStates()
379 */
380 public boolean hasAdditionalState(final String name) {
381 return additional.containsKey(name);
382 }
383
384 /** Check if two instances have the same set of additional states available.
385 * <p>
386 * Only the names and dimensions of the additional states are compared,
387 * not their values.
388 * </p>
389 * @param state state to compare to instance
390 * @exception MathIllegalStateException if an additional state does not have
391 * the same dimension in both states
392 */
393 public void ensureCompatibleAdditionalStates(final SpacecraftState state)
394 throws MathIllegalStateException {
395
396 // check instance additional states is a subset of the other one
397 for (final Map.Entry<String, double[]> entry : additional.entrySet()) {
398 final double[] other = state.additional.get(entry.getKey());
399 if (other == null) {
400 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
401 entry.getKey());
402 }
403 if (other.length != entry.getValue().length) {
404 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
405 other.length, entry.getValue().length);
406 }
407 }
408
409 if (state.additional.size() > additional.size()) {
410 // the other state has more additional states
411 for (final String name : state.additional.keySet()) {
412 if (!additional.containsKey(name)) {
413 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
414 name);
415 }
416 }
417 }
418
419 }
420
421 /** Get an additional state.
422 * @param name name of the additional state
423 * @return value of the additional state
424 * @see #addAdditionalState(String, double[])
425 * @see #hasAdditionalState(String)
426 * @see #getAdditionalStates()
427 */
428 public double[] getAdditionalState(final String name) {
429 if (!additional.containsKey(name)) {
430 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
431 }
432 return additional.get(name).clone();
433 }
434
435 /** Get an unmodifiable map of additional states.
436 * @return unmodifiable map of additional states
437 * @see #addAdditionalState(String, double[])
438 * @see #hasAdditionalState(String)
439 * @see #getAdditionalState(String)
440 */
441 public Map<String, double[]> getAdditionalStates() {
442 return Collections.unmodifiableMap(additional);
443 }
444
445 /** Compute the transform from orbite/attitude reference frame to spacecraft frame.
446 * <p>The spacecraft frame origin is at the point defined by the orbit,
447 * and its orientation is defined by the attitude.</p>
448 * @return transform from specified frame to current spacecraft frame
449 */
450 public Transform toTransform() {
451 final AbsoluteDate date = orbit.getDate();
452 return new Transform(date,
453 new Transform(date, orbit.getPVCoordinates().negate()),
454 new Transform(date, attitude.getOrientation()));
455 }
456
457 /** Get the central attraction coefficient.
458 * @return mu central attraction coefficient (m^3/s^2)
459 */
460 public double getMu() {
461 return orbit.getMu();
462 }
463
464 /** Get the Keplerian period.
465 * <p>The Keplerian period is computed directly from semi major axis
466 * and central acceleration constant.</p>
467 * @return Keplerian period in seconds
468 */
469 public double getKeplerianPeriod() {
470 return orbit.getKeplerianPeriod();
471 }
472
473 /** Get the Keplerian mean motion.
474 * <p>The Keplerian mean motion is computed directly from semi major axis
475 * and central acceleration constant.</p>
476 * @return Keplerian mean motion in radians per second
477 */
478 public double getKeplerianMeanMotion() {
479 return orbit.getKeplerianMeanMotion();
480 }
481
482 /** Get the semi-major axis.
483 * @return semi-major axis (m)
484 */
485 public double getA() {
486 return orbit.getA();
487 }
488
489 /** Get the first component of the eccentricity vector (as per equinoctial parameters).
490 * @return e cos(ω + Ω), first component of eccentricity vector
491 * @see #getE()
492 */
493 public double getEquinoctialEx() {
494 return orbit.getEquinoctialEx();
495 }
496
497 /** Get the second component of the eccentricity vector (as per equinoctial parameters).
498 * @return e sin(ω + Ω), second component of the eccentricity vector
499 * @see #getE()
500 */
501 public double getEquinoctialEy() {
502 return orbit.getEquinoctialEy();
503 }
504
505 /** Get the first component of the inclination vector (as per equinoctial parameters).
506 * @return tan(i/2) cos(Ω), first component of the inclination vector
507 * @see #getI()
508 */
509 public double getHx() {
510 return orbit.getHx();
511 }
512
513 /** Get the second component of the inclination vector (as per equinoctial parameters).
514 * @return tan(i/2) sin(Ω), second component of the inclination vector
515 * @see #getI()
516 */
517 public double getHy() {
518 return orbit.getHy();
519 }
520
521 /** Get the true longitude argument (as per equinoctial parameters).
522 * @return v + ω + Ω true longitude argument (rad)
523 * @see #getLE()
524 * @see #getLM()
525 */
526 public double getLv() {
527 return orbit.getLv();
528 }
529
530 /** Get the eccentric longitude argument (as per equinoctial parameters).
531 * @return E + ω + Ω eccentric longitude argument (rad)
532 * @see #getLv()
533 * @see #getLM()
534 */
535 public double getLE() {
536 return orbit.getLE();
537 }
538
539 /** Get the mean longitude argument (as per equinoctial parameters).
540 * @return M + ω + Ω mean longitude argument (rad)
541 * @see #getLv()
542 * @see #getLE()
543 */
544 public double getLM() {
545 return orbit.getLM();
546 }
547
548 // Additional orbital elements
549
550 /** Get the eccentricity.
551 * @return eccentricity
552 * @see #getEquinoctialEx()
553 * @see #getEquinoctialEy()
554 */
555 public double getE() {
556 return orbit.getE();
557 }
558
559 /** Get the inclination.
560 * @return inclination (rad)
561 * @see #getHx()
562 * @see #getHy()
563 */
564 public double getI() {
565 return orbit.getI();
566 }
567
568 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame.
569 * Compute the position and velocity of the satellite. This method caches its
570 * results, and recompute them only when the method is called with a new value
571 * for mu. The result is provided as a reference to the internally cached
572 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
573 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
574 * @return pvCoordinates in orbit definition frame
575 */
576 public TimeStampedPVCoordinates getPVCoordinates() {
577 return orbit.getPVCoordinates();
578 }
579
580 /** Get the {@link TimeStampedPVCoordinates} in given output frame.
581 * Compute the position and velocity of the satellite. This method caches its
582 * results, and recompute them only when the method is called with a new value
583 * for mu. The result is provided as a reference to the internally cached
584 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate
585 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while.
586 * @param outputFrame frame in which coordinates should be defined
587 * @return pvCoordinates in orbit definition frame
588 */
589 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
590 return orbit.getPVCoordinates(outputFrame);
591 }
592
593 /** Get the attitude.
594 * @return the attitude.
595 */
596 public Attitude getAttitude() {
597 return attitude;
598 }
599
600 /** Gets the current mass.
601 * @return the mass (kg)
602 */
603 public double getMass() {
604 return mass;
605 }
606
607 /** Replace the instance with a data transfer object for serialization.
608 * @return data transfer object that will be serialized
609 */
610 private Object writeReplace() {
611 return new DTO(this);
612 }
613
614 /** Internal class used only for serialization. */
615 private static class DTO implements Serializable {
616
617 /** Serializable UID. */
618 private static final long serialVersionUID = 20140617L;
619
620 /** Orbit. */
621 private final Orbit orbit;
622
623 /** Attitude and mass double values. */
624 private double[] d;
625
626 /** Additional states. */
627 private final Map<String, double[]> additional;
628
629 /** Simple constructor.
630 * @param state instance to serialize
631 */
632 private DTO(final SpacecraftState state) {
633
634 this.orbit = state.orbit;
635 this.additional = state.additional.isEmpty() ? null : state.additional;
636
637 final Rotation rotation = state.attitude.getRotation();
638 final Vector3D spin = state.attitude.getSpin();
639 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration();
640 this.d = new double[] {
641 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(),
642 spin.getX(), spin.getY(), spin.getZ(),
643 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(),
644 state.mass
645 };
646
647 }
648
649 /** Replace the deserialized data transfer object with a {@link SpacecraftState}.
650 * @return replacement {@link SpacecraftState}
651 */
652 private Object readResolve() {
653 return new SpacecraftState(orbit,
654 new Attitude(orbit.getFrame(),
655 new TimeStampedAngularCoordinates(orbit.getDate(),
656 new Rotation(d[0], d[1], d[2], d[3], false),
657 new Vector3D(d[4], d[5], d[6]),
658 new Vector3D(d[7], d[8], d[9]))),
659 d[10], additional);
660 }
661
662 }
663
664 }