AttitudeEndoints.java

  1. /* Copyright 2002-2022 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.Field;
  19. import org.hipparchus.CalculusFieldElement;
  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 AttitudeEndoints 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.     /** Complain if a field is null.
  64.      * @param field field to check
  65.      * @param key key associated with the field
  66.      */
  67.     private void checkNotNull(final Object field, final Enum<?> key) {
  68.         if (field == null) {
  69.             throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, key.name());
  70.         }
  71.     }
  72.     /** Check external frame is properly initialized.
  73.      * @param aKey key for frame A
  74.      * @param bKey key for frame B
  75.      */
  76.     public void checkExternalFrame(final Enum<?> aKey, final Enum<?> bKey) {
  77.         checkNotNull(frameA, aKey);
  78.         checkNotNull(frameB, bKey);
  79.         if (frameA.asSpacecraftBodyFrame() != null && frameB.asSpacecraftBodyFrame() != null) {
  80.             // we cannot have two spacecraft body frames
  81.             throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
  82.         }
  83.     }

  84.     /** Check is mandatory entries <em>except external frame</em> have been initialized.
  85.      * <p>
  86.      * Either frame A or frame B must be initialized with a {@link
  87.      * org.orekit.files.ccsds.definitions.SpacecraftBodyFrame spacecraft body frame}.
  88.      * </p>
  89.      * <p>
  90.      * This method should throw an exception if some mandatory entry is missing
  91.      * </p>
  92.      * @param aKey key for frame A
  93.      * @param bKey key for frame B
  94.      * @param dirKey key for direction
  95.      */
  96.     public void checkMandatoryEntriesExceptExternalFrame(final Enum<?> aKey, final Enum<?> bKey,
  97.                                                          final Enum<?> dirKey) {

  98.         if (frameA == null) {
  99.             if (frameB == null || frameB.asSpacecraftBodyFrame() == null) {
  100.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, aKey.name());
  101.             }
  102.         } else if (frameA.asSpacecraftBodyFrame() == null) {
  103.             if (frameB == null) {
  104.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, bKey.name());
  105.             } else if (frameB.asSpacecraftBodyFrame() == null) {
  106.                 // at least one of the frame must be a spacecraft body frame
  107.                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
  108.             }
  109.         }

  110.         checkNotNull(a2b, dirKey);

  111.     }

  112.     /** Set frame A.
  113.      * @param frameA frame A
  114.      */
  115.     public void setFrameA(final FrameFacade frameA) {
  116.         this.frameA = frameA;
  117.     }

  118.     /** Get frame A.
  119.      * @return frame A
  120.      */
  121.     public FrameFacade getFrameA() {
  122.         return frameA;
  123.     }

  124.     /** Set frame B.
  125.      * @param frameB frame B
  126.      */
  127.     public void setFrameB(final FrameFacade frameB) {
  128.         this.frameB = frameB;
  129.     }

  130.     /** Get frame B.
  131.      * @return frame B
  132.      */
  133.     public FrameFacade getFrameB() {
  134.         return frameB;
  135.     }

  136.     /** Set rotation direction.
  137.      * @param a2b if true, rotation is from {@link #getFrameA() frame A}
  138.      * to {@link #getFrameB() frame B}
  139.      */
  140.     public void setA2b(final boolean a2b) {
  141.         this.a2b = a2b;
  142.     }

  143.     /** Check if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}.
  144.      * @return true if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}
  145.      */
  146.     public boolean isA2b() {
  147.         return a2b == null ? true : a2b;
  148.     }

  149.     /** Get the external frame.
  150.      * @return external frame
  151.      */
  152.     public FrameFacade getExternalFrame() {
  153.         return frameA.asSpacecraftBodyFrame() == null ? frameA : frameB;
  154.     }

  155.     /** Get the spacecraft body frame.
  156.      * @return spacecraft body frame
  157.      */
  158.     public FrameFacade getSpacecraftBodyFrame() {
  159.         return frameA.asSpacecraftBodyFrame() == null ? frameB : frameA;
  160.     }

  161.     /** Check if attitude is from external frame to spacecraft body frame.
  162.      * <p>
  163.      * {@link #checkMandatoryEntriesExceptExternalFrame(Enum, Enum, Enum)
  164.      * Mandatory entries} must have been initialized properly to non-null
  165.      * values before this method is called, otherwise {@code NullPointerException}
  166.      * will be thrown.
  167.      * </p>
  168.      * @return true if attitude is from external frame to spacecraft body frame
  169.      */
  170.     public boolean isExternal2SpacecraftBody() {
  171.         return a2b ^ frameB.asSpacecraftBodyFrame() == null;
  172.     }

  173.     /** Check if a endpoint is compatible with another one.
  174.      * <p>
  175.      * Endpoins are compatible if they refer o the same frame names,
  176.      * in the same order and in the same direction.
  177.      * </p>
  178.      * @param other other endpoints to check against
  179.      * @return true if both endpoints are compatible with each other
  180.      */
  181.     public boolean isCompatibleWith(final AttitudeEndoints other) {
  182.         return frameA.getName().equals(other.frameA.getName()) &&
  183.                frameB.getName().equals(other.frameB.getName()) &&
  184.                a2b.equals(other.a2b);
  185.     }

  186.     /**  {@inheritDoc} */
  187.     @Override
  188.     public Attitude build(final Frame frame, final PVCoordinatesProvider pvProv,
  189.                           final TimeStampedAngularCoordinates rawAttitude) {

  190.         // attitude converted to Orekit conventions
  191.         final TimeStampedAngularCoordinates att =
  192.                         isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();

  193.         final FrameFacade        external = getExternalFrame();
  194.         final OrbitRelativeFrame orf      = external.asOrbitRelativeFrame();
  195.         if (orf != null) {
  196.             // this is an orbit-relative attitude
  197.             if (orf.getLofType() == null) {
  198.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
  199.             }

  200.             // construction of the local orbital frame, using PV from reference frame
  201.             final PVCoordinates pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
  202.             final AngularCoordinates frame2Lof =
  203.                             orf.isQuasiInertial() ?
  204.                             new AngularCoordinates(orf.getLofType().rotationFromInertial(pv), Vector3D.ZERO) :
  205.                             orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();

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

  208.         } else {
  209.             // this is an absolute attitude
  210.             if (external.asFrame() == null) {
  211.                 // unknown frame
  212.                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
  213.             }
  214.             final Attitude attitude = new Attitude(external.asFrame(), att);
  215.             return frame == null ? attitude : attitude.withReferenceFrame(frame);
  216.         }

  217.     }

  218.     /**  {@inheritDoc} */
  219.     @Override
  220.     public <T extends CalculusFieldElement<T>>
  221.         FieldAttitude<T> build(final Frame frame, final FieldPVCoordinatesProvider<T> pvProv,
  222.                                final TimeStampedFieldAngularCoordinates<T> rawAttitude) {

  223.         // attitude converted to Orekit conventions
  224.         final TimeStampedFieldAngularCoordinates<T> att =
  225.                         isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();

  226.         final FrameFacade        external = getExternalFrame();
  227.         final OrbitRelativeFrame orf      = external.asOrbitRelativeFrame();
  228.         if (orf != null) {
  229.             // this is an orbit-relative attitude
  230.             if (orf.getLofType() == null) {
  231.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
  232.             }

  233.             // construction of the local orbital frame, using PV from reference frame
  234.             final FieldPVCoordinates<T> pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
  235.             final Field<T> field = rawAttitude.getDate().getField();
  236.             final FieldAngularCoordinates<T> referenceToLof =
  237.                             orf.isQuasiInertial() ?
  238.                             new FieldAngularCoordinates<>(orf.getLofType().rotationFromInertial(field, pv),
  239.                                                           FieldVector3D.getZero(field)) :
  240.                             orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();

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

  243.         } else {
  244.             // this is an absolute attitude
  245.             if (external.asFrame() == null) {
  246.                 // this should never happen as all CelestialBodyFrame have an Orekit mapping
  247.                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
  248.             }
  249.             final FieldAttitude<T> attitude = new FieldAttitude<>(external.asFrame(), att);
  250.             return frame == null ? attitude : attitude.withReferenceFrame(frame);
  251.         }

  252.     }

  253.     /** {@inheritDoc} */
  254.     @Override
  255.     public String toString() {
  256.         return frameA.getName() + (isA2b() ? " → " : " ← ") + frameB.getName();
  257.     }

  258. }