1 /* Copyright 2002-2024 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(), pv.negate(), attitude.getOrientation()); 754 } 755 756 /** Compute the static transform from state defining frame to spacecraft frame. 757 * @return static transform from specified frame to current spacecraft frame 758 * @see #toTransform() 759 * @since 12.0 760 */ 761 public StaticTransform toStaticTransform() { 762 return StaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation()); 763 } 764 765 /** Get the central attraction coefficient. 766 * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the 767 * state contains an absolute position-velocity-acceleration rather than an orbit 768 */ 769 public double getMu() { 770 return isOrbitDefined() ? orbit.getMu() : Double.NaN; 771 } 772 773 /** Get the Keplerian period. 774 * <p>The Keplerian period is computed directly from semi major axis 775 * and central acceleration constant.</p> 776 * @return Keplerian period in seconds, or {code Double.NaN} if the 777 * state contains an absolute position-velocity-acceleration rather 778 * than an orbit 779 */ 780 public double getKeplerianPeriod() { 781 return isOrbitDefined() ? orbit.getKeplerianPeriod() : Double.NaN; 782 } 783 784 /** Get the Keplerian mean motion. 785 * <p>The Keplerian mean motion is computed directly from semi major axis 786 * and central acceleration constant.</p> 787 * @return Keplerian mean motion in radians per second, or {code Double.NaN} if the 788 * state contains an absolute position-velocity-acceleration rather 789 * than an orbit 790 */ 791 public double getKeplerianMeanMotion() { 792 return isOrbitDefined() ? orbit.getKeplerianMeanMotion() : Double.NaN; 793 } 794 795 /** Get the semi-major axis. 796 * @return semi-major axis (m), or {code Double.NaN} if the 797 * state contains an absolute position-velocity-acceleration rather 798 * than an orbit 799 */ 800 public double getA() { 801 return isOrbitDefined() ? orbit.getA() : Double.NaN; 802 } 803 804 /** Get the first component of the eccentricity vector (as per equinoctial parameters). 805 * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the 806 * state contains an absolute position-velocity-acceleration rather 807 * than an orbit 808 * @see #getE() 809 */ 810 public double getEquinoctialEx() { 811 return isOrbitDefined() ? orbit.getEquinoctialEx() : Double.NaN; 812 } 813 814 /** Get the second component of the eccentricity vector (as per equinoctial parameters). 815 * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the 816 * state contains an absolute position-velocity-acceleration rather 817 * than an orbit 818 * @see #getE() 819 */ 820 public double getEquinoctialEy() { 821 return isOrbitDefined() ? orbit.getEquinoctialEy() : Double.NaN; 822 } 823 824 /** Get the first component of the inclination vector (as per equinoctial parameters). 825 * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the 826 * state contains an absolute position-velocity-acceleration rather 827 * than an orbit 828 * @see #getI() 829 */ 830 public double getHx() { 831 return isOrbitDefined() ? orbit.getHx() : Double.NaN; 832 } 833 834 /** Get the second component of the inclination vector (as per equinoctial parameters). 835 * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the 836 * state contains an absolute position-velocity-acceleration rather 837 * than an orbit 838 * @see #getI() 839 */ 840 public double getHy() { 841 return isOrbitDefined() ? orbit.getHy() : Double.NaN; 842 } 843 844 /** Get the true latitude argument (as per equinoctial parameters). 845 * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the 846 * state contains an absolute position-velocity-acceleration rather 847 * than an orbit 848 * @see #getLE() 849 * @see #getLM() 850 */ 851 public double getLv() { 852 return isOrbitDefined() ? orbit.getLv() : Double.NaN; 853 } 854 855 /** Get the eccentric latitude argument (as per equinoctial parameters). 856 * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the 857 * state contains an absolute position-velocity-acceleration rather 858 * than an orbit 859 * @see #getLv() 860 * @see #getLM() 861 */ 862 public double getLE() { 863 return isOrbitDefined() ? orbit.getLE() : Double.NaN; 864 } 865 866 /** Get the mean longitude argument (as per equinoctial parameters). 867 * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the 868 * state contains an absolute position-velocity-acceleration rather 869 * than an orbit 870 * @see #getLv() 871 * @see #getLE() 872 */ 873 public double getLM() { 874 return isOrbitDefined() ? orbit.getLM() : Double.NaN; 875 } 876 877 // Additional orbital elements 878 879 /** Get the eccentricity. 880 * @return eccentricity, or {code Double.NaN} if the 881 * state contains an absolute position-velocity-acceleration rather 882 * than an orbit 883 * @see #getEquinoctialEx() 884 * @see #getEquinoctialEy() 885 */ 886 public double getE() { 887 return isOrbitDefined() ? orbit.getE() : Double.NaN; 888 } 889 890 /** Get the inclination. 891 * @return inclination (rad) 892 * @see #getHx() 893 * @see #getHy() 894 */ 895 public double getI() { 896 return isOrbitDefined() ? orbit.getI() : Double.NaN; 897 } 898 899 /** Get the position in orbit definition frame. 900 * @return position in orbit definition frame 901 * @since 12.0 902 * @see #getPVCoordinates() 903 */ 904 public Vector3D getPosition() { 905 return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition(); 906 } 907 908 /** Get the {@link TimeStampedPVCoordinates} in orbit definition frame. 909 * <p> 910 * Compute the position and velocity of the satellite. This method caches its 911 * results, and recompute them only when the method is called with a new value 912 * for mu. The result is provided as a reference to the internally cached 913 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate 914 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while. 915 * </p> 916 * @return pvCoordinates in orbit definition frame 917 */ 918 public TimeStampedPVCoordinates getPVCoordinates() { 919 return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates(); 920 } 921 922 /** Get the position in given output frame. 923 * @param outputFrame frame in which position should be defined 924 * @return position in given output frame 925 * @since 12.0 926 * @see #getPVCoordinates(Frame) 927 */ 928 public Vector3D getPosition(final Frame outputFrame) { 929 return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame); 930 } 931 932 /** Get the {@link TimeStampedPVCoordinates} in given output frame. 933 * <p> 934 * Compute the position and velocity of the satellite. This method caches its 935 * results, and recompute them only when the method is called with a new value 936 * for mu. The result is provided as a reference to the internally cached 937 * {@link TimeStampedPVCoordinates}, so the caller is responsible to copy it in a separate 938 * {@link TimeStampedPVCoordinates} if it needs to keep the value for a while. 939 * </p> 940 * @param outputFrame frame in which coordinates should be defined 941 * @return pvCoordinates in orbit definition frame 942 */ 943 public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) { 944 return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame); 945 } 946 947 /** Get the attitude. 948 * @return the attitude. 949 */ 950 public Attitude getAttitude() { 951 return attitude; 952 } 953 954 /** Gets the current mass. 955 * @return the mass (kg) 956 */ 957 public double getMass() { 958 return mass; 959 } 960 961 /** Replace the instance with a data transfer object for serialization. 962 * @return data transfer object that will be serialized 963 */ 964 private Object writeReplace() { 965 return isOrbitDefined() ? new DTOO(this) : new DTOA(this); 966 } 967 968 /** Internal class used only for serialization. */ 969 private static class DTOO implements Serializable { 970 971 /** Serializable UID. */ 972 private static final long serialVersionUID = 20211121L; 973 974 /** Orbit. */ 975 private final Orbit orbit; 976 977 /** Attitude and mass double values. */ 978 private double[] d; 979 980 /** Additional states. */ 981 private final DoubleArrayDictionary additional; 982 983 /** Additional states derivatives. */ 984 private final DoubleArrayDictionary additionalDot; 985 986 /** Simple constructor. 987 * @param state instance to serialize 988 */ 989 private DTOO(final SpacecraftState state) { 990 991 this.orbit = state.orbit; 992 this.additional = state.additional.getData().isEmpty() ? null : state.additional; 993 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot; 994 995 final Rotation rotation = state.attitude.getRotation(); 996 final Vector3D spin = state.attitude.getSpin(); 997 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration(); 998 this.d = new double[] { 999 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(), 1000 spin.getX(), spin.getY(), spin.getZ(), 1001 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(), 1002 state.mass 1003 }; 1004 1005 } 1006 1007 /** Replace the de-serialized data transfer object with a {@link SpacecraftState}. 1008 * @return replacement {@link SpacecraftState} 1009 */ 1010 private Object readResolve() { 1011 return new SpacecraftState(orbit, 1012 new Attitude(orbit.getFrame(), 1013 new TimeStampedAngularCoordinates(orbit.getDate(), 1014 new Rotation(d[0], d[1], d[2], d[3], false), 1015 new Vector3D(d[4], d[5], d[6]), 1016 new Vector3D(d[7], d[8], d[9]))), 1017 d[10], additional, additionalDot); 1018 } 1019 1020 } 1021 1022 /** Internal class used only for serialization. */ 1023 private static class DTOA implements Serializable { 1024 1025 /** Serializable UID. */ 1026 private static final long serialVersionUID = 20211121L; 1027 1028 /** Absolute position-velocity-acceleration. */ 1029 private final AbsolutePVCoordinates absPva; 1030 1031 /** Attitude and mass double values. */ 1032 private double[] d; 1033 1034 /** Additional states. */ 1035 private final DoubleArrayDictionary additional; 1036 1037 /** Additional states derivatives. */ 1038 private final DoubleArrayDictionary additionalDot; 1039 1040 /** Simple constructor. 1041 * @param state instance to serialize 1042 */ 1043 private DTOA(final SpacecraftState state) { 1044 1045 this.absPva = state.absPva; 1046 this.additional = state.additional.getData().isEmpty() ? null : state.additional; 1047 this.additionalDot = state.additionalDot.getData().isEmpty() ? null : state.additionalDot; 1048 1049 final Rotation rotation = state.attitude.getRotation(); 1050 final Vector3D spin = state.attitude.getSpin(); 1051 final Vector3D rotationAcceleration = state.attitude.getRotationAcceleration(); 1052 this.d = new double[] { 1053 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3(), 1054 spin.getX(), spin.getY(), spin.getZ(), 1055 rotationAcceleration.getX(), rotationAcceleration.getY(), rotationAcceleration.getZ(), 1056 state.mass 1057 }; 1058 1059 } 1060 1061 /** Replace the deserialized data transfer object with a {@link SpacecraftState}. 1062 * @return replacement {@link SpacecraftState} 1063 */ 1064 private Object readResolve() { 1065 return new SpacecraftState(absPva, 1066 new Attitude(absPva.getFrame(), 1067 new TimeStampedAngularCoordinates(absPva.getDate(), 1068 new Rotation(d[0], d[1], d[2], d[3], false), 1069 new Vector3D(d[4], d[5], d[6]), 1070 new Vector3D(d[7], d[8], d[9]))), 1071 d[10], additional, additionalDot); 1072 } 1073 } 1074 1075 @Override 1076 public String toString() { 1077 return "SpacecraftState{" + 1078 "orbit=" + orbit + 1079 ", attitude=" + attitude + 1080 ", mass=" + mass + 1081 ", additional=" + additional + 1082 ", additionalDot=" + additionalDot + 1083 '}'; 1084 } 1085 }