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