1 /* Copyright 2002-2023 CS GROUP 2 * Licensed to CS GROUP (CS) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * CS licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.orekit.propagation; 18 19 import java.io.Serializable; 20 21 import org.hipparchus.exception.LocalizedCoreFormats; 22 import org.hipparchus.exception.MathIllegalStateException; 23 import org.hipparchus.geometry.euclidean.threed.Rotation; 24 import org.hipparchus.geometry.euclidean.threed.Vector3D; 25 import org.hipparchus.util.FastMath; 26 import org.orekit.attitudes.Attitude; 27 import org.orekit.attitudes.AttitudeProvider; 28 import org.orekit.attitudes.FrameAlignedProvider; 29 import org.orekit.errors.OrekitException; 30 import org.orekit.errors.OrekitIllegalArgumentException; 31 import org.orekit.errors.OrekitIllegalStateException; 32 import org.orekit.errors.OrekitMessages; 33 import org.orekit.frames.Frame; 34 import org.orekit.frames.StaticTransform; 35 import org.orekit.frames.Transform; 36 import org.orekit.orbits.Orbit; 37 import org.orekit.time.AbsoluteDate; 38 import org.orekit.time.TimeShiftable; 39 import org.orekit.time.TimeStamped; 40 import org.orekit.utils.AbsolutePVCoordinates; 41 import org.orekit.utils.DoubleArrayDictionary; 42 import org.orekit.utils.TimeStampedAngularCoordinates; 43 import org.orekit.utils.TimeStampedPVCoordinates; 44 45 /** This class is the representation of a complete state holding orbit, attitude 46 * and mass information at a given date, meant primarily for propagation. 47 * 48 * <p>It contains an {@link Orbit}, or an {@link AbsolutePVCoordinates} if there 49 * is no definite central body, plus the current mass and attitude at the intrinsic 50 * {@link AbsoluteDate}. Quantities are guaranteed to be consistent in terms 51 * of date and reference frame. The spacecraft state may also contain additional 52 * states, which are simply named double arrays which can hold any user-defined 53 * data. 54 * </p> 55 * <p> 56 * The state can be slightly shifted to close dates. This actual shift varies 57 * between {@link Orbit} and {@link AbsolutePVCoordinates}. 58 * For attitude it is a linear extrapolation taking the spin rate into account 59 * and no mass change. It is <em>not</em> intended as a replacement for proper 60 * orbit and attitude propagation but should be sufficient for either small 61 * time shifts or coarse accuracy. 62 * </p> 63 * <p> 64 * The instance <code>SpacecraftState</code> is guaranteed to be immutable. 65 * </p> 66 * @see org.orekit.propagation.numerical.NumericalPropagator 67 * @author Fabien Maussion 68 * @author Véronique Pommier-Maurussane 69 * @author Luc Maisonobe 70 */ 71 public class SpacecraftState 72 implements TimeStamped, TimeShiftable<SpacecraftState>, Serializable { 73 74 /** Default mass. */ 75 public static final double DEFAULT_MASS = 1000.0; 76 77 /** Serializable UID. */ 78 private static final long serialVersionUID = 20211119L; 79 80 /** 81 * tolerance on date comparison in {@link #checkConsistency(Orbit, Attitude)}. 100 ns 82 * corresponds to sub-mm accuracy at LEO orbital velocities. 83 */ 84 private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9; 85 86 /** Orbital state. */ 87 private final Orbit orbit; 88 89 /** Trajectory state, when it is not an orbit. */ 90 private final AbsolutePVCoordinates absPva; 91 92 /** Attitude. */ 93 private final Attitude attitude; 94 95 /** Current mass (kg). */ 96 private final double mass; 97 98 /** Additional states. */ 99 private final DoubleArrayDictionary additional; 100 101 /** Additional states derivatives. 102 * @since 11.1 103 */ 104 private final DoubleArrayDictionary additionalDot; 105 106 /** Build a spacecraft state from orbit only. 107 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p> 108 * @param orbit the orbit 109 */ 110 public SpacecraftState(final Orbit orbit) { 111 this(orbit, getDefaultAttitudeProvider(orbit.getFrame()) 112 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 113 DEFAULT_MASS, (DoubleArrayDictionary) null); 114 } 115 116 /** Build a spacecraft state from orbit and attitude. 117 * <p>Mass is set to an unspecified non-null arbitrary value.</p> 118 * @param orbit the orbit 119 * @param attitude attitude 120 * @exception IllegalArgumentException if orbit and attitude dates 121 * or frames are not equal 122 */ 123 public SpacecraftState(final Orbit orbit, final Attitude attitude) 124 throws IllegalArgumentException { 125 this(orbit, attitude, DEFAULT_MASS, (DoubleArrayDictionary) null); 126 } 127 128 /** Create a new instance from orbit and mass. 129 * <p>Attitude law is set to an unspecified default attitude.</p> 130 * @param orbit the orbit 131 * @param mass the mass (kg) 132 */ 133 public SpacecraftState(final Orbit orbit, final double mass) { 134 this(orbit, getDefaultAttitudeProvider(orbit.getFrame()) 135 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 136 mass, (DoubleArrayDictionary) null); 137 } 138 139 /** Build a spacecraft state from orbit, attitude 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, (DoubleArrayDictionary) null); 149 } 150 151 /** Build a spacecraft state from orbit and additional states. 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 * @since 11.1 156 */ 157 public SpacecraftState(final Orbit orbit, final DoubleArrayDictionary additional) { 158 this(orbit, getDefaultAttitudeProvider(orbit.getFrame()) 159 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 160 DEFAULT_MASS, additional); 161 } 162 163 /** Build a spacecraft state from orbit, attitude and additional states. 164 * <p>Mass is set to an unspecified non-null arbitrary value.</p> 165 * @param orbit the orbit 166 * @param attitude attitude 167 * @param additional additional states 168 * @exception IllegalArgumentException if orbit and attitude dates 169 * or frames are not equal 170 * @since 11.1 171 */ 172 public SpacecraftState(final Orbit orbit, final Attitude attitude, final DoubleArrayDictionary additional) 173 throws IllegalArgumentException { 174 this(orbit, attitude, DEFAULT_MASS, additional); 175 } 176 177 /** Create a new instance from orbit, mass and additional states. 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 * @since 11.1 183 */ 184 public SpacecraftState(final Orbit orbit, final double mass, final DoubleArrayDictionary additional) { 185 this(orbit, getDefaultAttitudeProvider(orbit.getFrame()) 186 .getAttitude(orbit, orbit.getDate(), orbit.getFrame()), 187 mass, additional); 188 } 189 190 /** Build a spacecraft state from orbit, attitude, mass and additional states. 191 * @param orbit the orbit 192 * @param attitude attitude 193 * @param mass the mass (kg) 194 * @param additional additional states (may be null if no additional states are available) 195 * @exception IllegalArgumentException if orbit and attitude dates 196 * or frames are not equal 197 * @since 11.1 198 */ 199 public SpacecraftState(final Orbit orbit, final Attitude attitude, 200 final double mass, final DoubleArrayDictionary additional) 201 throws IllegalArgumentException { 202 this(orbit, attitude, mass, additional, null); 203 } 204 205 /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives. 206 * @param orbit the orbit 207 * @param attitude attitude 208 * @param mass the mass (kg) 209 * @param additional additional states (may be null if no additional states are available) 210 * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available) 211 * @exception IllegalArgumentException if orbit and attitude dates 212 * or frames are not equal 213 * @since 11.1 214 */ 215 public SpacecraftState(final Orbit orbit, final Attitude attitude, final double mass, 216 final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot) 217 throws IllegalArgumentException { 218 checkConsistency(orbit, attitude); 219 this.orbit = orbit; 220 this.absPva = null; 221 this.attitude = attitude; 222 this.mass = mass; 223 if (additional == null) { 224 this.additional = new DoubleArrayDictionary(); 225 } else { 226 this.additional = additional; 227 } 228 if (additionalDot == null) { 229 this.additionalDot = new DoubleArrayDictionary(); 230 } else { 231 this.additionalDot = new DoubleArrayDictionary(additionalDot); 232 } 233 } 234 235 /** Build a spacecraft state from position-velocity-acceleration only. 236 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p> 237 * @param absPva position-velocity-acceleration 238 */ 239 public SpacecraftState(final AbsolutePVCoordinates absPva) { 240 this(absPva, getDefaultAttitudeProvider(absPva.getFrame()) 241 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()), 242 DEFAULT_MASS, (DoubleArrayDictionary) null); 243 } 244 245 /** Build a spacecraft state from position-velocity-acceleration and attitude. 246 * <p>Mass is set to an unspecified non-null arbitrary value.</p> 247 * @param absPva position-velocity-acceleration 248 * @param attitude attitude 249 * @exception IllegalArgumentException if orbit and attitude dates 250 * or frames are not equal 251 */ 252 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude) 253 throws IllegalArgumentException { 254 this(absPva, attitude, DEFAULT_MASS, (DoubleArrayDictionary) null); 255 } 256 257 /** Create a new instance from position-velocity-acceleration and mass. 258 * <p>Attitude law is set to an unspecified default attitude.</p> 259 * @param absPva position-velocity-acceleration 260 * @param mass the mass (kg) 261 */ 262 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass) { 263 this(absPva, getDefaultAttitudeProvider(absPva.getFrame()) 264 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()), 265 mass, (DoubleArrayDictionary) null); 266 } 267 268 /** Build a spacecraft state from position-velocity-acceleration, attitude and mass. 269 * @param absPva position-velocity-acceleration 270 * @param attitude attitude 271 * @param mass the mass (kg) 272 * @exception IllegalArgumentException if orbit and attitude dates 273 * or frames are not equal 274 */ 275 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass) 276 throws IllegalArgumentException { 277 this(absPva, attitude, mass, (DoubleArrayDictionary) null); 278 } 279 280 /** Build a spacecraft state from position-velocity-acceleration and additional states. 281 * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p> 282 * @param absPva position-velocity-acceleration 283 * @param additional additional states 284 * @since 11.1 285 */ 286 public SpacecraftState(final AbsolutePVCoordinates absPva, final DoubleArrayDictionary additional) { 287 this(absPva, getDefaultAttitudeProvider(absPva.getFrame()) 288 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()), 289 DEFAULT_MASS, additional); 290 } 291 292 /** Build a spacecraft state from position-velocity-acceleration, attitude and additional states. 293 * <p>Mass is set to an unspecified non-null arbitrary value.</p> 294 * @param absPva position-velocity-acceleration 295 * @param attitude attitude 296 * @param additional additional states 297 * @exception IllegalArgumentException if orbit and attitude dates 298 * or frames are not equal 299 * @since 11.1 300 */ 301 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final DoubleArrayDictionary additional) 302 throws IllegalArgumentException { 303 this(absPva, attitude, DEFAULT_MASS, additional); 304 } 305 306 /** Create a new instance from position-velocity-acceleration, mass and additional states. 307 * <p>Attitude law is set to an unspecified default attitude.</p> 308 * @param absPva position-velocity-acceleration 309 * @param mass the mass (kg) 310 * @param additional additional states 311 * @since 11.1 312 */ 313 public SpacecraftState(final AbsolutePVCoordinates absPva, final double mass, final DoubleArrayDictionary additional) { 314 this(absPva, getDefaultAttitudeProvider(absPva.getFrame()) 315 .getAttitude(absPva, absPva.getDate(), absPva.getFrame()), 316 mass, additional); 317 } 318 319 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states. 320 * @param absPva position-velocity-acceleration 321 * @param attitude attitude 322 * @param mass the mass (kg) 323 * @param additional additional states (may be null if no additional states are available) 324 * @exception IllegalArgumentException if orbit and attitude dates 325 * or frames are not equal 326 * @since 11.1 327 */ 328 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, 329 final double mass, final DoubleArrayDictionary additional) 330 throws IllegalArgumentException { 331 this(absPva, attitude, mass, additional, null); 332 } 333 334 /** Build a spacecraft state from position-velocity-acceleration, attitude, mass and additional states and derivatives. 335 * @param absPva position-velocity-acceleration 336 * @param attitude attitude 337 * @param mass the mass (kg) 338 * @param additional additional states (may be null if no additional states are available) 339 * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available) 340 * @exception IllegalArgumentException if orbit and attitude dates 341 * or frames are not equal 342 * @since 11.1 343 */ 344 public SpacecraftState(final AbsolutePVCoordinates absPva, final Attitude attitude, final double mass, 345 final DoubleArrayDictionary additional, final DoubleArrayDictionary additionalDot) 346 throws IllegalArgumentException { 347 checkConsistency(absPva, attitude); 348 this.orbit = null; 349 this.absPva = absPva; 350 this.attitude = attitude; 351 this.mass = mass; 352 if (additional == null) { 353 this.additional = new DoubleArrayDictionary(); 354 } else { 355 this.additional = new DoubleArrayDictionary(additional); 356 } 357 if (additionalDot == null) { 358 this.additionalDot = new DoubleArrayDictionary(); 359 } else { 360 this.additionalDot = new DoubleArrayDictionary(additionalDot); 361 } 362 } 363 364 /** Add an additional state. 365 * <p> 366 * {@link SpacecraftState SpacecraftState} instances are immutable, 367 * so this method does <em>not</em> change the instance, but rather 368 * creates a new instance, which has the same orbit, attitude, mass 369 * and additional states as the original instance, except it also 370 * has the specified state. If the original instance already had an 371 * additional state with the same name, it will be overridden. If it 372 * did not have any additional state with that name, the new instance 373 * will have one more additional state than the original instance. 374 * </p> 375 * @param name name of the additional state (names containing "orekit" 376 * with any case are reserved for the library internal use) 377 * @param value value of the additional state 378 * @return a new instance, with the additional state added 379 * @see #hasAdditionalState(String) 380 * @see #getAdditionalState(String) 381 * @see #getAdditionalStatesValues() 382 */ 383 public SpacecraftState addAdditionalState(final String name, final double... value) { 384 final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additional); 385 newDict.put(name, value.clone()); 386 if (isOrbitDefined()) { 387 return new SpacecraftState(orbit, attitude, mass, newDict, additionalDot); 388 } else { 389 return new SpacecraftState(absPva, attitude, mass, newDict, additionalDot); 390 } 391 } 392 393 /** Add an additional state derivative. 394 * <p> 395 * {@link SpacecraftState SpacecraftState} instances are immutable, 396 * so this method does <em>not</em> change the instance, but rather 397 * creates a new instance, which has the same components as the original 398 * instance, except it also has the specified state derivative. If the 399 * original instance already had an additional state derivative with the 400 * same name, it will be overridden. If it did not have any additional 401 * state derivative with that name, the new instance will have one more 402 * additional state derivative than the original instance. 403 * </p> 404 * @param name name of the additional state derivative (names containing "orekit" 405 * with any case are reserved for the library internal use) 406 * @param value value of the additional state derivative 407 * @return a new instance, with the additional state added 408 * @see #hasAdditionalStateDerivative(String) 409 * @see #getAdditionalStateDerivative(String) 410 * @see #getAdditionalStatesDerivatives() 411 * @since 11.1 412 */ 413 public SpacecraftState addAdditionalStateDerivative(final String name, final double... value) { 414 final DoubleArrayDictionary newDict = new DoubleArrayDictionary(additionalDot); 415 newDict.put(name, value.clone()); 416 if (isOrbitDefined()) { 417 return new SpacecraftState(orbit, attitude, mass, additional, newDict); 418 } else { 419 return new SpacecraftState(absPva, attitude, mass, additional, newDict); 420 } 421 } 422 423 /** Check orbit and attitude dates are equal. 424 * @param orbit the orbit 425 * @param attitude attitude 426 * @exception IllegalArgumentException if orbit and attitude dates 427 * are not equal 428 */ 429 private static void checkConsistency(final Orbit orbit, final Attitude attitude) 430 throws IllegalArgumentException { 431 if (FastMath.abs(orbit.getDate().durationFrom(attitude.getDate())) > 432 DATE_INCONSISTENCY_THRESHOLD) { 433 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH, 434 orbit.getDate(), attitude.getDate()); 435 } 436 if (orbit.getFrame() != attitude.getReferenceFrame()) { 437 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH, 438 orbit.getFrame().getName(), 439 attitude.getReferenceFrame().getName()); 440 } 441 } 442 443 /** Defines provider for default Attitude when not passed to constructor. 444 * Currently chosen arbitrarily as aligned with input frame. 445 * It is also used in {@link FieldSpacecraftState}. 446 * @param frame reference frame 447 * @return default attitude provider 448 * @since 12.0 449 */ 450 static AttitudeProvider getDefaultAttitudeProvider(final Frame frame) { 451 return new FrameAlignedProvider(frame); 452 } 453 454 /** Check if the state contains an orbit part. 455 * <p> 456 * A state contains either an {@link AbsolutePVCoordinates absolute 457 * position-velocity-acceleration} or an {@link Orbit orbit}. 458 * </p> 459 * @return true if state contains an orbit (in which case {@link #getOrbit()} 460 * will not throw an exception), or false if the state contains an 461 * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()} 462 * will not throw an exception) 463 */ 464 public boolean isOrbitDefined() { 465 return orbit != null; 466 } 467 468 /** Check AbsolutePVCoordinates and attitude dates are equal. 469 * @param absPva position-velocity-acceleration 470 * @param attitude attitude 471 * @exception IllegalArgumentException if orbit and attitude dates 472 * are not equal 473 */ 474 private static void checkConsistency(final AbsolutePVCoordinates absPva, final Attitude attitude) 475 throws IllegalArgumentException { 476 if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())) > 477 DATE_INCONSISTENCY_THRESHOLD) { 478 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH, 479 absPva.getDate(), attitude.getDate()); 480 } 481 if (absPva.getFrame() != attitude.getReferenceFrame()) { 482 throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH, 483 absPva.getFrame().getName(), 484 attitude.getReferenceFrame().getName()); 485 } 486 } 487 488 /** Get a time-shifted state. 489 * <p> 490 * The state can be slightly shifted to close dates. This shift is based on 491 * simple models. For orbits, the model is a Keplerian one if no derivatives 492 * are available in the orbit, or Keplerian plus quadratic effect of the 493 * non-Keplerian acceleration if derivatives are available. For attitude, 494 * a polynomial model is used. Neither mass nor additional states change. 495 * Shifting is <em>not</em> intended as a replacement for proper orbit 496 * and attitude propagation but should be sufficient for small time shifts 497 * or coarse accuracy. 498 * </p> 499 * <p> 500 * As a rough order of magnitude, the following table shows the extrapolation 501 * errors obtained between this simple shift method and an {@link 502 * org.orekit.propagation.numerical.NumericalPropagator numerical 503 * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field, 504 * Sun and Moon third bodies attractions, drag and solar radiation pressure. 505 * Beware that these results will be different for other orbits. 506 * </p> 507 * <table border="1"> 508 * <caption>Extrapolation Error</caption> 509 * <tr style="background-color: #ccccff"><th>interpolation time (s)</th> 510 * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr> 511 * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td> 18</td><td> 1.1</td></tr> 512 * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td> 72</td><td> 9.1</td></tr> 513 * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr> 514 * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr> 515 * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr> 516 * </table> 517 * @param dt time shift in seconds 518 * @return a new state, shifted with respect to the instance (which is immutable) 519 * except for the mass and additional states which stay unchanged 520 */ 521 @Override 522 public SpacecraftState shiftedBy(final double dt) { 523 if (isOrbitDefined()) { 524 return new SpacecraftState(orbit.shiftedBy(dt), attitude.shiftedBy(dt), 525 mass, shiftAdditional(dt), additionalDot); 526 } else { 527 return new SpacecraftState(absPva.shiftedBy(dt), attitude.shiftedBy(dt), 528 mass, shiftAdditional(dt), additionalDot); 529 } 530 } 531 532 /** Shift additional states. 533 * @param dt time shift in seconds 534 * @return shifted additional states 535 * @since 11.1.1 536 */ 537 private DoubleArrayDictionary shiftAdditional(final double dt) { 538 539 // fast handling when there are no derivatives at all 540 if (additionalDot.size() == 0) { 541 return additional; 542 } 543 544 // there are derivatives, we need to take them into account in the additional state 545 final DoubleArrayDictionary shifted = new DoubleArrayDictionary(additional); 546 for (final DoubleArrayDictionary.Entry dotEntry : additionalDot.getData()) { 547 final DoubleArrayDictionary.Entry entry = shifted.getEntry(dotEntry.getKey()); 548 if (entry != null) { 549 entry.scaledIncrement(dt, dotEntry); 550 } 551 } 552 553 return shifted; 554 555 } 556 557 /** Get the absolute position-velocity-acceleration. 558 * <p> 559 * A state contains either an {@link AbsolutePVCoordinates absolute 560 * position-velocity-acceleration} or an {@link Orbit orbit}. Which 561 * one is present can be checked using {@link #isOrbitDefined()}. 562 * </p> 563 * @return absolute position-velocity-acceleration 564 * @exception OrekitIllegalStateException if position-velocity-acceleration is null, 565 * which mean the state rather contains an {@link Orbit} 566 * @see #isOrbitDefined() 567 * @see #getOrbit() 568 */ 569 public AbsolutePVCoordinates getAbsPVA() throws OrekitIllegalStateException { 570 if (isOrbitDefined()) { 571 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES); 572 } 573 return absPva; 574 } 575 576 /** Get the current orbit. 577 * <p> 578 * A state contains either an {@link AbsolutePVCoordinates absolute 579 * position-velocity-acceleration} or an {@link Orbit orbit}. Which 580 * one is present can be checked using {@link #isOrbitDefined()}. 581 * </p> 582 * @return the orbit 583 * @exception OrekitIllegalStateException if orbit is null, 584 * which means the state rather contains an {@link AbsolutePVCoordinates absolute 585 * position-velocity-acceleration} 586 * @see #isOrbitDefined() 587 * @see #getAbsPVA() 588 */ 589 public Orbit getOrbit() throws OrekitIllegalStateException { 590 if (orbit == null) { 591 throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT); 592 } 593 return orbit; 594 } 595 596 /** {@inheritDoc} */ 597 @Override 598 public AbsoluteDate getDate() { 599 return (absPva == null) ? orbit.getDate() : absPva.getDate(); 600 } 601 602 /** Get the defining frame. 603 * @return the frame in which state is defined 604 */ 605 public Frame getFrame() { 606 return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame(); 607 } 608 609 /** Check if an additional state is available. 610 * @param name name of the additional state 611 * @return true if the additional state is available 612 * @see #addAdditionalState(String, double[]) 613 * @see #getAdditionalState(String) 614 * @see #getAdditionalStatesValues() 615 */ 616 public boolean hasAdditionalState(final String name) { 617 return additional.getEntry(name) != null; 618 } 619 620 /** Check if an additional state derivative is available. 621 * @param name name of the additional state derivative 622 * @return true if the additional state derivative is available 623 * @see #addAdditionalStateDerivative(String, double[]) 624 * @see #getAdditionalStateDerivative(String) 625 * @see #getAdditionalStatesDerivatives() 626 * @since 11.1 627 */ 628 public boolean hasAdditionalStateDerivative(final String name) { 629 return additionalDot.getEntry(name) != null; 630 } 631 632 /** Check if two instances have the same set of additional states available. 633 * <p> 634 * Only the names and dimensions of the additional states are compared, 635 * not their values. 636 * </p> 637 * @param state state to compare to instance 638 * @exception MathIllegalStateException if an additional state does not have 639 * the same dimension in both states 640 */ 641 public void ensureCompatibleAdditionalStates(final SpacecraftState state) 642 throws MathIllegalStateException { 643 644 // check instance additional states is a subset of the other one 645 for (final DoubleArrayDictionary.Entry entry : additional.getData()) { 646 final double[] other = state.additional.get(entry.getKey()); 647 if (other == null) { 648 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, 649 entry.getKey()); 650 } 651 if (other.length != entry.getValue().length) { 652 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 653 other.length, entry.getValue().length); 654 } 655 } 656 657 // check instance additional states derivatives is a subset of the other one 658 for (final DoubleArrayDictionary.Entry entry : additionalDot.getData()) { 659 final double[] other = state.additionalDot.get(entry.getKey()); 660 if (other == null) { 661 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, 662 entry.getKey()); 663 } 664 if (other.length != entry.getValue().length) { 665 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, 666 other.length, entry.getValue().length); 667 } 668 } 669 670 if (state.additional.size() > additional.size()) { 671 // the other state has more additional states 672 for (final DoubleArrayDictionary.Entry entry : state.additional.getData()) { 673 if (additional.getEntry(entry.getKey()) == null) { 674 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, 675 entry.getKey()); 676 } 677 } 678 } 679 680 if (state.additionalDot.size() > additionalDot.size()) { 681 // the other state has more additional states 682 for (final DoubleArrayDictionary.Entry entry : state.additionalDot.getData()) { 683 if (additionalDot.getEntry(entry.getKey()) == null) { 684 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, 685 entry.getKey()); 686 } 687 } 688 } 689 690 } 691 692 /** Get an additional state. 693 * @param name name of the additional state 694 * @return value of the additional state 695 * @see #addAdditionalState(String, double[]) 696 * @see #hasAdditionalState(String) 697 * @see #getAdditionalStatesValues() 698 */ 699 public double[] getAdditionalState(final String name) { 700 final DoubleArrayDictionary.Entry entry = additional.getEntry(name); 701 if (entry == null) { 702 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name); 703 } 704 return entry.getValue(); 705 } 706 707 /** Get an additional state derivative. 708 * @param name name of the additional state derivative 709 * @return value of the additional state derivative 710 * @see #addAdditionalStateDerivative(String, double[]) 711 * @see #hasAdditionalStateDerivative(String) 712 * @see #getAdditionalStatesDerivatives() 713 * @since 11.1 714 */ 715 public double[] getAdditionalStateDerivative(final String name) { 716 final DoubleArrayDictionary.Entry entry = additionalDot.getEntry(name); 717 if (entry == null) { 718 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name); 719 } 720 return entry.getValue(); 721 } 722 723 /** Get an unmodifiable map of additional states. 724 * @return unmodifiable map of additional states 725 * @see #addAdditionalState(String, double[]) 726 * @see #hasAdditionalState(String) 727 * @see #getAdditionalState(String) 728 * @since 11.1 729 */ 730 public DoubleArrayDictionary getAdditionalStatesValues() { 731 return additional.unmodifiableView(); 732 } 733 734 /** Get an unmodifiable map of additional states derivatives. 735 * @return unmodifiable map of additional states derivatives 736 * @see #addAdditionalStateDerivative(String, double[]) 737 * @see #hasAdditionalStateDerivative(String) 738 * @see #getAdditionalStateDerivative(String) 739 * @since 11.1 740 */ 741 public DoubleArrayDictionary getAdditionalStatesDerivatives() { 742 return additionalDot.unmodifiableView(); 743 } 744 745 /** Compute the transform from state defining frame to spacecraft frame. 746 * <p>The spacecraft frame origin is at the point defined by the orbit 747 * (or absolute position-velocity-acceleration), and its orientation is 748 * defined by the attitude.</p> 749 * @return transform from specified frame to current spacecraft frame 750 */ 751 public Transform toTransform() { 752 final TimeStampedPVCoordinates pv = getPVCoordinates(); 753 return new Transform(pv.getDate(), 754 new Transform(pv.getDate(), pv.negate()), 755 new Transform(pv.getDate(), attitude.getOrientation())); 756 } 757 758 /** Compute the static transform from state defining frame to spacecraft frame. 759 * @return static transform from specified frame to current spacecraft frame 760 * @see #toTransform() 761 * @since 12.0 762 */ 763 public StaticTransform toStaticTransform() { 764 return StaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation()); 765 } 766 767 /** Get the central attraction coefficient. 768 * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the 769 * state contains an absolute position-velocity-acceleration rather than an orbit 770 */ 771 public double getMu() { 772 return isOrbitDefined() ? orbit.getMu() : Double.NaN; 773 } 774 775 /** Get the Keplerian period. 776 * <p>The Keplerian period is computed directly from semi major axis 777 * and central acceleration constant.</p> 778 * @return Keplerian period in seconds, or {code Double.NaN} if the 779 * state contains an absolute position-velocity-acceleration rather 780 * than an orbit 781 */ 782 public double getKeplerianPeriod() { 783 return isOrbitDefined() ? orbit.getKeplerianPeriod() : Double.NaN; 784 } 785 786 /** Get the Keplerian mean motion. 787 * <p>The Keplerian mean motion is computed directly from semi major axis 788 * and central acceleration constant.</p> 789 * @return Keplerian mean motion in radians per second, or {code Double.NaN} if the 790 * state contains an absolute position-velocity-acceleration rather 791 * than an orbit 792 */ 793 public double getKeplerianMeanMotion() { 794 return isOrbitDefined() ? orbit.getKeplerianMeanMotion() : Double.NaN; 795 } 796 797 /** Get the semi-major axis. 798 * @return semi-major axis (m), or {code Double.NaN} if the 799 * state contains an absolute position-velocity-acceleration rather 800 * than an orbit 801 */ 802 public double getA() { 803 return isOrbitDefined() ? orbit.getA() : Double.NaN; 804 } 805 806 /** Get the first component of the eccentricity vector (as per equinoctial parameters). 807 * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the 808 * state contains an absolute position-velocity-acceleration rather 809 * than an orbit 810 * @see #getE() 811 */ 812 public double getEquinoctialEx() { 813 return isOrbitDefined() ? orbit.getEquinoctialEx() : Double.NaN; 814 } 815 816 /** Get the second component of the eccentricity vector (as per equinoctial parameters). 817 * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the 818 * state contains an absolute position-velocity-acceleration rather 819 * than an orbit 820 * @see #getE() 821 */ 822 public double getEquinoctialEy() { 823 return isOrbitDefined() ? orbit.getEquinoctialEy() : Double.NaN; 824 } 825 826 /** Get the first component of the inclination vector (as per equinoctial parameters). 827 * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the 828 * state contains an absolute position-velocity-acceleration rather 829 * than an orbit 830 * @see #getI() 831 */ 832 public double getHx() { 833 return isOrbitDefined() ? orbit.getHx() : Double.NaN; 834 } 835 836 /** Get the second component of the inclination vector (as per equinoctial parameters). 837 * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the 838 * state contains an absolute position-velocity-acceleration rather 839 * than an orbit 840 * @see #getI() 841 */ 842 public double getHy() { 843 return isOrbitDefined() ? orbit.getHy() : Double.NaN; 844 } 845 846 /** Get the true latitude argument (as per equinoctial parameters). 847 * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the 848 * state contains an absolute position-velocity-acceleration rather 849 * than an orbit 850 * @see #getLE() 851 * @see #getLM() 852 */ 853 public double getLv() { 854 return isOrbitDefined() ? orbit.getLv() : Double.NaN; 855 } 856 857 /** Get the eccentric latitude argument (as per equinoctial parameters). 858 * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the 859 * state contains an absolute position-velocity-acceleration rather 860 * than an orbit 861 * @see #getLv() 862 * @see #getLM() 863 */ 864 public double getLE() { 865 return isOrbitDefined() ? orbit.getLE() : Double.NaN; 866 } 867 868 /** Get the mean longitude argument (as per equinoctial parameters). 869 * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the 870 * state contains an absolute position-velocity-acceleration rather 871 * than an orbit 872 * @see #getLv() 873 * @see #getLE() 874 */ 875 public double getLM() { 876 return isOrbitDefined() ? orbit.getLM() : Double.NaN; 877 } 878 879 // Additional orbital elements 880 881 /** Get the eccentricity. 882 * @return eccentricity, or {code Double.NaN} if the 883 * state contains an absolute position-velocity-acceleration rather 884 * than an orbit 885 * @see #getEquinoctialEx() 886 * @see #getEquinoctialEy() 887 */ 888 public double getE() { 889 return isOrbitDefined() ? orbit.getE() : Double.NaN; 890 } 891 892 /** Get the inclination. 893 * @return inclination (rad) 894 * @see #getHx() 895 * @see #getHy() 896 */ 897 public double getI() { 898 return isOrbitDefined() ? orbit.getI() : Double.NaN; 899 } 900 901 /** Get the position in orbit definition frame. 902 * @return position in orbit definition frame 903 * @since 12.0 904 * @see #getPVCoordinates() 905 */ 906 public Vector3D getPosition() { 907 return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition(); 908 } 909 910 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame. 911 * <p> 912 * Compute the position and velocity of the satellite. This method caches its 913 * results, and recompute them only when the method is called with a new value 914 * for mu. The result is provided as a reference to the internally cached 915 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate 916 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while. 917 * </p> 918 * @return pvCoordinates in orbit definition frame 919 */ 920 public TimeStampedPVCoordinates getPVCoordinates() { 921 return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates(); 922 } 923 924 /** Get the position in given output frame. 925 * @param outputFrame frame in which position should be defined 926 * @return position in given output frame 927 * @since 12.0 928 * @see #getPVCoordinates(Frame) 929 */ 930 public Vector3D getPosition(final Frame outputFrame) { 931 return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame); 932 } 933 934 /** Get the {@link TimeStampedPVCoordinates} in given output frame. 935 * <p> 936 * Compute the position and velocity of the satellite. This method caches its 937 * results, and recompute them only when the method is called with a new value 938 * for mu. The result is provided as a reference to the internally cached 939 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate 940 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while. 941 * </p> 942 * @param outputFrame frame in which coordinates should be defined 943 * @return pvCoordinates in orbit definition frame 944 */ 945 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) { 946 return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame); 947 } 948 949 /** Get the attitude. 950 * @return the attitude. 951 */ 952 public Attitude getAttitude() { 953 return attitude; 954 } 955 956 /** Gets the current mass. 957 * @return the mass (kg) 958 */ 959 public double getMass() { 960 return mass; 961 } 962 963 /** Replace the instance with a data transfer object for serialization. 964 * @return data transfer object that will be serialized 965 */ 966 private Object writeReplace() { 967 return isOrbitDefined() ? new DTOO(this) : new DTOA(this); 968 } 969 970 /** Internal class used only for serialization. */ 971 private static class DTOO implements Serializable { 972 973 /** Serializable UID. */ 974 private static final long serialVersionUID = 20211121L; 975 976 /** Orbit. */ 977 private final Orbit orbit; 978 979 /** Attitude and mass double values. */ 980 private double[] d; 981 982 /** Additional states. */ 983 private final DoubleArrayDictionary additional; 984 985 /** Additional states derivatives. */ 986 private final DoubleArrayDictionary additionalDot; 987 988 /** Simple constructor. 989 * @param state instance to serialize 990 */ 991 private DTOO(final SpacecraftState state) { 992 993 this.orbit = state.orbit; 994 this.additional = state.additional.getData().isEmpty() ? null : state.additional; 995 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot; 996 997 final Rotation rotation = state.attitude.getRotation(); 998 final Vector3D spin = state.attitude.getSpin(); 999 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration(); 1000 this.d = new double[] { 1001 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(), 1002 spin.getX(), spin.getY(), spin.getZ(), 1003 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(), 1004 state.mass 1005 }; 1006 1007 } 1008 1009 /** Replace the de-serialized data transfer object with a {@link SpacecraftState}. 1010 * @return replacement {@link SpacecraftState} 1011 */ 1012 private Object readResolve() { 1013 return new SpacecraftState(orbit, 1014 new Attitude(orbit.getFrame(), 1015 new TimeStampedAngularCoordinates(orbit.getDate(), 1016 new Rotation(d[0], d[1], d[2], d[3], false), 1017 new Vector3D(d[4], d[5], d[6]), 1018 new Vector3D(d[7], d[8], d[9]))), 1019 d[10], additional, additionalDot); 1020 } 1021 1022 } 1023 1024 /** Internal class used only for serialization. */ 1025 private static class DTOA implements Serializable { 1026 1027 /** Serializable UID. */ 1028 private static final long serialVersionUID = 20211121L; 1029 1030 /** Absolute position-velocity-acceleration. */ 1031 private final AbsolutePVCoordinates absPva; 1032 1033 /** Attitude and mass double values. */ 1034 private double[] d; 1035 1036 /** Additional states. */ 1037 private final DoubleArrayDictionary additional; 1038 1039 /** Additional states derivatives. */ 1040 private final DoubleArrayDictionary additionalDot; 1041 1042 /** Simple constructor. 1043 * @param state instance to serialize 1044 */ 1045 private DTOA(final SpacecraftState state) { 1046 1047 this.absPva = state.absPva; 1048 this.additional = state.additional.getData().isEmpty() ? null : state.additional; 1049 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot; 1050 1051 final Rotation rotation = state.attitude.getRotation(); 1052 final Vector3D spin = state.attitude.getSpin(); 1053 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration(); 1054 this.d = new double[] { 1055 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(), 1056 spin.getX(), spin.getY(), spin.getZ(), 1057 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(), 1058 state.mass 1059 }; 1060 1061 } 1062 1063 /** Replace the deserialized data transfer object with a {@link SpacecraftState}. 1064 * @return replacement {@link SpacecraftState} 1065 */ 1066 private Object readResolve() { 1067 return new SpacecraftState(absPva, 1068 new Attitude(absPva.getFrame(), 1069 new TimeStampedAngularCoordinates(absPva.getDate(), 1070 new Rotation(d[0], d[1], d[2], d[3], false), 1071 new Vector3D(d[4], d[5], d[6]), 1072 new Vector3D(d[7], d[8], d[9]))), 1073 d[10], additional, additionalDot); 1074 } 1075 } 1076 1077 @Override 1078 public String toString() { 1079 return "SpacecraftState{" + 1080 "orbit=" + orbit + 1081 ", attitude=" + attitude + 1082 ", mass=" + mass + 1083 ", additional=" + additional + 1084 ", additionalDot=" + additionalDot + 1085 '}'; 1086 } 1087 }