AttitudeEndpoints.java

  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.files.ccsds.ndm.adm;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.orekit.attitudes.Attitude;
  23. import org.orekit.attitudes.AttitudeBuilder;
  24. import org.orekit.attitudes.FieldAttitude;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.files.ccsds.definitions.FrameFacade;
  28. import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
  29. import org.orekit.frames.Frame;
  30. import org.orekit.utils.AngularCoordinates;
  31. import org.orekit.utils.FieldAngularCoordinates;
  32. import org.orekit.utils.FieldPVCoordinates;
  33. import org.orekit.utils.FieldPVCoordinatesProvider;
  34. import org.orekit.utils.PVCoordinates;
  35. import org.orekit.utils.PVCoordinatesProvider;
  36. import org.orekit.utils.TimeStampedAngularCoordinates;
  37. import org.orekit.utils.TimeStampedFieldAngularCoordinates;

  38. /** Endpoints for attitude definition.
  39.  * <p>
  40.  * This class provides a bridge between two different views of attitude definition.
  41.  * In both views, there is an external frame, based on either celestial body or orbit-relative
  42.  * and there is a spacecraft body frame.
  43.  * <ul>
  44.  *   <li>CCSDS ADM view: frames are labeled as A and B but nothing tells which is which
  45.  *   and attitude can be defined in any direction</li>
  46.  *   <li>{@link Attitude Orekit attitude} view: attitude is always from external to
  47.  *   spacecraft body</li>
  48.  * </ul>
  49.  * @author Luc Maisonobe
  50.  * @since 11.0
  51.  */
  52. public class AttitudeEndpoints implements AttitudeBuilder {

  53.     /** Constant for A → B diraction. */
  54.     public static final String A2B = "A2B";

  55.     /** Constant for A ← B direction. */
  56.     public static final String B2A = "B2A";

  57.     /** Frame A. */
  58.     private FrameFacade frameA;

  59.     /** Frame B. */
  60.     private FrameFacade frameB;

  61.     /** Flag for frames direction. */
  62.     private Boolean a2b;

  63.     /** Empty constructor.
  64.      * <p>
  65.      * This constructor is not strictly necessary, but it prevents spurious
  66.      * javadoc warnings with JDK 18 and later.
  67.      * </p>
  68.      * @since 12.0
  69.      */
  70.     public AttitudeEndpoints() {
  71.         // nothing to do
  72.     }

  73.     /** Complain if a field is null.
  74.      * @param field field to check
  75.      * @param key key associated with the field
  76.      */
  77.     private void checkNotNull(final Object field, final Enum<?> key) {
  78.         if (field == null) {
  79.             throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, key.name());
  80.         }
  81.     }
  82.     /** Check external frame is properly initialized.
  83.      * @param aKey key for frame A
  84.      * @param bKey key for frame B
  85.      */
  86.     public void checkExternalFrame(final Enum<?> aKey, final Enum<?> bKey) {
  87.         checkNotNull(frameA, aKey);
  88.         checkNotNull(frameB, bKey);
  89.         if (frameA.asSpacecraftBodyFrame() != null && frameB.asSpacecraftBodyFrame() != null) {
  90.             // we cannot have two spacecraft body frames
  91.             throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
  92.         }
  93.     }

  94.     /** Check is mandatory entries <em>except external frame</em> have been initialized.
  95.      * <p>
  96.      * Either frame A or frame B must be initialized with a {@link
  97.      * org.orekit.files.ccsds.definitions.SpacecraftBodyFrame spacecraft body frame}.
  98.      * </p>
  99.      * <p>
  100.      * This method should throw an exception if some mandatory entry is missing
  101.      * </p>
  102.      * @param version format version
  103.      * @param aKey key for frame A
  104.      * @param bKey key for frame B
  105.      * @param dirKey key for direction
  106.      */
  107.     public void checkMandatoryEntriesExceptExternalFrame(final double version,
  108.                                                          final Enum<?> aKey, final Enum<?> bKey,
  109.                                                          final Enum<?> dirKey) {

  110.         if (frameA == null) {
  111.             if (frameB == null || frameB.asSpacecraftBodyFrame() == null) {
  112.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, aKey.name());
  113.             }
  114.         } else if (frameA.asSpacecraftBodyFrame() == null) {
  115.             if (frameB == null) {
  116.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, bKey.name());
  117.             } else if (frameB.asSpacecraftBodyFrame() == null) {
  118.                 // at least one of the frame must be a spacecraft body frame
  119.                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
  120.             }
  121.         }

  122.         if (version < 2.0) {
  123.             // in ADM version 1, direction is mandatory
  124.             checkNotNull(a2b, dirKey);
  125.         } else if (!isA2b()) {
  126.             // in ADM version 2, direction is always A → B
  127.             throw new OrekitException(OrekitMessages.CCSDS_KEYWORD_NOT_ALLOWED_IN_VERSION,
  128.                                       dirKey, version);
  129.         }

  130.     }

  131.     /** Set frame A.
  132.      * @param frameA frame A
  133.      */
  134.     public void setFrameA(final FrameFacade frameA) {
  135.         this.frameA = frameA;
  136.     }

  137.     /** Get frame A.
  138.      * @return frame A
  139.      */
  140.     public FrameFacade getFrameA() {
  141.         return frameA;
  142.     }

  143.     /** Set frame B.
  144.      * @param frameB frame B
  145.      */
  146.     public void setFrameB(final FrameFacade frameB) {
  147.         this.frameB = frameB;
  148.     }

  149.     /** Get frame B.
  150.      * @return frame B
  151.      */
  152.     public FrameFacade getFrameB() {
  153.         return frameB;
  154.     }

  155.     /** Set rotation direction.
  156.      * @param a2b if true, rotation is from {@link #getFrameA() frame A}
  157.      * to {@link #getFrameB() frame B}
  158.      */
  159.     public void setA2b(final boolean a2b) {
  160.         this.a2b = a2b;
  161.     }

  162.     /** Check if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}.
  163.      * @return true if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}
  164.      */
  165.     public boolean isA2b() {
  166.         return a2b == null ? true : a2b;
  167.     }

  168.     /** Get the external frame.
  169.      * @return external frame
  170.      */
  171.     public FrameFacade getExternalFrame() {
  172.         return frameA.asSpacecraftBodyFrame() == null ? frameA : frameB;
  173.     }

  174.     /** Get the spacecraft body frame.
  175.      * @return spacecraft body frame
  176.      */
  177.     public FrameFacade getSpacecraftBodyFrame() {
  178.         return frameA.asSpacecraftBodyFrame() == null ? frameB : frameA;
  179.     }

  180.     /** Check if attitude is from external frame to spacecraft body frame.
  181.      * <p>
  182.      * {@link #checkMandatoryEntriesExceptExternalFrame(double, Enum, Enum, Enum)
  183.      * Mandatory entries} must have been initialized properly to non-null
  184.      * values before this method is called, otherwise {@code NullPointerException}
  185.      * will be thrown.
  186.      * </p>
  187.      * @return true if attitude is from external frame to spacecraft body frame
  188.      */
  189.     public boolean isExternal2SpacecraftBody() {
  190.         return isA2b() ^ frameB.asSpacecraftBodyFrame() == null;
  191.     }

  192.     /** Check if a endpoint is compatible with another one.
  193.      * <p>
  194.      * Endpoins are compatible if they refer o the same frame names,
  195.      * in the same order and in the same direction.
  196.      * </p>
  197.      * @param other other endpoints to check against
  198.      * @return true if both endpoints are compatible with each other
  199.      */
  200.     public boolean isCompatibleWith(final AttitudeEndpoints other) {
  201.         return frameA.getName().equals(other.frameA.getName()) &&
  202.                frameB.getName().equals(other.frameB.getName()) &&
  203.                a2b.equals(other.a2b);
  204.     }

  205.     /**  {@inheritDoc} */
  206.     @Override
  207.     public Attitude build(final Frame frame, final PVCoordinatesProvider pvProv,
  208.                           final TimeStampedAngularCoordinates rawAttitude) {

  209.         // attitude converted to Orekit conventions
  210.         final TimeStampedAngularCoordinates att =
  211.                         isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();

  212.         final FrameFacade        external = getExternalFrame();
  213.         final OrbitRelativeFrame orf      = external.asOrbitRelativeFrame();
  214.         if (orf != null) {
  215.             // this is an orbit-relative attitude
  216.             if (orf.getLofType() == null) {
  217.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
  218.             }

  219.             // construction of the local orbital frame, using PV from reference frame
  220.             final PVCoordinates pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
  221.             final AngularCoordinates frame2Lof =
  222.                             orf.isQuasiInertial() ?
  223.                             new AngularCoordinates(orf.getLofType().rotationFromInertial(pv), Vector3D.ZERO) :
  224.                             orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();

  225.             // compose with APM
  226.             return new Attitude(frame, att.addOffset(frame2Lof));

  227.         } else {
  228.             // this is an absolute attitude
  229.             if (external.asFrame() == null) {
  230.                 // unknown frame
  231.                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
  232.             }
  233.             final Attitude attitude = new Attitude(external.asFrame(), att);
  234.             return frame == null ? attitude : attitude.withReferenceFrame(frame);
  235.         }

  236.     }

  237.     /**  {@inheritDoc} */
  238.     @Override
  239.     public <T extends CalculusFieldElement<T>>
  240.         FieldAttitude<T> build(final Frame frame, final FieldPVCoordinatesProvider<T> pvProv,
  241.                                final TimeStampedFieldAngularCoordinates<T> rawAttitude) {

  242.         // attitude converted to Orekit conventions
  243.         final TimeStampedFieldAngularCoordinates<T> att =
  244.                         isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();

  245.         final FrameFacade        external = getExternalFrame();
  246.         final OrbitRelativeFrame orf      = external.asOrbitRelativeFrame();
  247.         if (orf != null) {
  248.             // this is an orbit-relative attitude
  249.             if (orf.getLofType() == null) {
  250.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
  251.             }

  252.             // construction of the local orbital frame, using PV from reference frame
  253.             final FieldPVCoordinates<T> pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
  254.             final Field<T> field = rawAttitude.getDate().getField();
  255.             final FieldAngularCoordinates<T> referenceToLof =
  256.                             orf.isQuasiInertial() ?
  257.                             new FieldAngularCoordinates<>(orf.getLofType().rotationFromInertial(field, pv),
  258.                                                           FieldVector3D.getZero(field)) :
  259.                             orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();

  260.             // compose with APM
  261.             return new FieldAttitude<>(frame, att.addOffset(referenceToLof));

  262.         } else {
  263.             // this is an absolute attitude
  264.             if (external.asFrame() == null) {
  265.                 // this should never happen as all CelestialBodyFrame have an Orekit mapping
  266.                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
  267.             }
  268.             final FieldAttitude<T> attitude = new FieldAttitude<>(external.asFrame(), att);
  269.             return frame == null ? attitude : attitude.withReferenceFrame(frame);
  270.         }

  271.     }

  272.     /** {@inheritDoc} */
  273.     @Override
  274.     public String toString() {
  275.         return frameA.getName() + (isA2b() ? " → " : " ← ") + frameB.getName();
  276.     }

  277. }