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