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  
19  import org.hipparchus.Field;
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.orekit.attitudes.Attitude;
24  import org.orekit.attitudes.AttitudeBuilder;
25  import org.orekit.attitudes.FieldAttitude;
26  import org.orekit.errors.OrekitException;
27  import org.orekit.errors.OrekitMessages;
28  import org.orekit.files.ccsds.definitions.FrameFacade;
29  import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
30  import org.orekit.frames.Frame;
31  import org.orekit.utils.AngularCoordinates;
32  import org.orekit.utils.FieldAngularCoordinates;
33  import org.orekit.utils.FieldPVCoordinates;
34  import org.orekit.utils.FieldPVCoordinatesProvider;
35  import org.orekit.utils.PVCoordinates;
36  import org.orekit.utils.PVCoordinatesProvider;
37  import org.orekit.utils.TimeStampedAngularCoordinates;
38  import org.orekit.utils.TimeStampedFieldAngularCoordinates;
39  
40  /** Endpoints for attitude definition.
41   * <p>
42   * This class provides a bridge between two different views of attitude definition.
43   * In both views, there is an external frame, based on either celestial body or orbit-relative
44   * and there is a spacecraft body frame.
45   * <ul>
46   *   <li>CCSDS ADM view: frames are labeled as A and B but nothing tells which is which
47   *   and attitude can be defined in any direction</li>
48   *   <li>{@link Attitude Orekit attitude} view: attitude is always from external to
49   *   spacecraft body</li>
50   * </ul>
51   * @author Luc Maisonobe
52   * @since 11.0
53   */
54  public class AttitudeEndoints implements AttitudeBuilder {
55  
56      /** Constant for A → B diraction. */
57      public static final String A2B = "A2B";
58  
59      /** Constant for A ← B direction. */
60      public static final String B2A = "B2A";
61  
62      /** Frame A. */
63      private FrameFacade frameA;
64  
65      /** Frame B. */
66      private FrameFacade frameB;
67  
68      /** Flag for frames direction. */
69      private Boolean a2b;
70  
71      /** Complain if a field is null.
72       * @param field field to check
73       * @param key key associated with the field
74       */
75      private void checkNotNull(final Object field, final Enum<?> key) {
76          if (field == null) {
77              throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, key.name());
78          }
79      }
80      /** Check external frame is properly initialized.
81       * @param aKey key for frame A
82       * @param bKey key for frame B
83       */
84      public void checkExternalFrame(final Enum<?> aKey, final Enum<?> bKey) {
85          checkNotNull(frameA, aKey);
86          checkNotNull(frameB, bKey);
87          if (frameA.asSpacecraftBodyFrame() != null && frameB.asSpacecraftBodyFrame() != null) {
88              // we cannot have two spacecraft body frames
89              throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
90          }
91      }
92  
93      /** Check is mandatory entries <em>except external frame</em> have been initialized.
94       * <p>
95       * Either frame A or frame B must be initialized with a {@link
96       * org.orekit.files.ccsds.definitions.SpacecraftBodyFrame spacecraft body frame}.
97       * </p>
98       * <p>
99       * This method should throw an exception if some mandatory entry is missing
100      * </p>
101      * @param aKey key for frame A
102      * @param bKey key for frame B
103      * @param dirKey key for direction
104      */
105     public void checkMandatoryEntriesExceptExternalFrame(final Enum<?> aKey, final Enum<?> bKey,
106                                                          final Enum<?> dirKey) {
107 
108         if (frameA == null) {
109             if (frameB == null || frameB.asSpacecraftBodyFrame() == null) {
110                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, aKey.name());
111             }
112         } else if (frameA.asSpacecraftBodyFrame() == null) {
113             if (frameB == null) {
114                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, bKey.name());
115             } else if (frameB.asSpacecraftBodyFrame() == null) {
116                 // at least one of the frame must be a spacecraft body frame
117                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, frameB.getName());
118             }
119         }
120 
121         checkNotNull(a2b, dirKey);
122 
123     }
124 
125     /** Set frame A.
126      * @param frameA frame A
127      */
128     public void setFrameA(final FrameFacade frameA) {
129         this.frameA = frameA;
130     }
131 
132     /** Get frame A.
133      * @return frame A
134      */
135     public FrameFacade getFrameA() {
136         return frameA;
137     }
138 
139     /** Set frame B.
140      * @param frameB frame B
141      */
142     public void setFrameB(final FrameFacade frameB) {
143         this.frameB = frameB;
144     }
145 
146     /** Get frame B.
147      * @return frame B
148      */
149     public FrameFacade getFrameB() {
150         return frameB;
151     }
152 
153     /** Set rotation direction.
154      * @param a2b if true, rotation is from {@link #getFrameA() frame A}
155      * to {@link #getFrameB() frame B}
156      */
157     public void setA2b(final boolean a2b) {
158         this.a2b = a2b;
159     }
160 
161     /** Check if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}.
162      * @return true if rotation direction is from {@link #getFrameA() frame A} to {@link #getFrameB() frame B}
163      */
164     public boolean isA2b() {
165         return a2b == null ? true : a2b;
166     }
167 
168     /** Get the external frame.
169      * @return external frame
170      */
171     public FrameFacade getExternalFrame() {
172         return frameA.asSpacecraftBodyFrame() == null ? frameA : frameB;
173     }
174 
175     /** Get the spacecraft body frame.
176      * @return spacecraft body frame
177      */
178     public FrameFacade getSpacecraftBodyFrame() {
179         return frameA.asSpacecraftBodyFrame() == null ? frameB : frameA;
180     }
181 
182     /** Check if attitude is from external frame to spacecraft body frame.
183      * <p>
184      * {@link #checkMandatoryEntriesExceptExternalFrame(Enum, Enum, Enum)
185      * Mandatory entries} must have been initialized properly to non-null
186      * values before this method is called, otherwise {@code NullPointerException}
187      * will be thrown.
188      * </p>
189      * @return true if attitude is from external frame to spacecraft body frame
190      */
191     public boolean isExternal2SpacecraftBody() {
192         return a2b ^ frameB.asSpacecraftBodyFrame() == null;
193     }
194 
195     /** Check if a endpoint is compatible with another one.
196      * <p>
197      * Endpoins are compatible if they refer o the same frame names,
198      * in the same order and in the same direction.
199      * </p>
200      * @param other other endpoints to check against
201      * @return true if both endpoints are compatible with each other
202      */
203     public boolean isCompatibleWith(final AttitudeEndoints other) {
204         return frameA.getName().equals(other.frameA.getName()) &&
205                frameB.getName().equals(other.frameB.getName()) &&
206                a2b.equals(other.a2b);
207     }
208 
209     /**  {@inheritDoc} */
210     @Override
211     public Attitude build(final Frame frame, final PVCoordinatesProvider pvProv,
212                           final TimeStampedAngularCoordinates rawAttitude) {
213 
214         // attitude converted to Orekit conventions
215         final TimeStampedAngularCoordinates att =
216                         isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();
217 
218         final FrameFacade        external = getExternalFrame();
219         final OrbitRelativeFrame orf      = external.asOrbitRelativeFrame();
220         if (orf != null) {
221             // this is an orbit-relative attitude
222             if (orf.getLofType() == null) {
223                 throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
224             }
225 
226             // construction of the local orbital frame, using PV from reference frame
227             final PVCoordinates pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
228             final AngularCoordinates frame2Lof =
229                             orf.isQuasiInertial() ?
230                             new AngularCoordinates(orf.getLofType().rotationFromInertial(pv), Vector3D.ZERO) :
231                             orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();
232 
233             // compose with APM
234             return new Attitude(frame, att.addOffset(frame2Lof));
235 
236         } else {
237             // this is an absolute attitude
238             if (external.asFrame() == null) {
239                 // unknown frame
240                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
241             }
242             final Attitude attitude = new Attitude(external.asFrame(), att);
243             return frame == null ? attitude : attitude.withReferenceFrame(frame);
244         }
245 
246     }
247 
248     /**  {@inheritDoc} */
249     @Override
250     public <T extends CalculusFieldElement<T>>
251         FieldAttitude<T> build(final Frame frame, final FieldPVCoordinatesProvider<T> pvProv,
252                                final TimeStampedFieldAngularCoordinates<T> rawAttitude) {
253 
254         // attitude converted to Orekit conventions
255         final TimeStampedFieldAngularCoordinates<T> att =
256                         isExternal2SpacecraftBody() ? rawAttitude : rawAttitude.revert();
257 
258         final FrameFacade        external = getExternalFrame();
259         final OrbitRelativeFrame orf      = external.asOrbitRelativeFrame();
260         if (orf != null) {
261             // this is an orbit-relative attitude
262             if (orf.getLofType() == null) {
263                 throw new OrekitException(OrekitMessages.UNSUPPORTED_LOCAL_ORBITAL_FRAME, external.getName());
264             }
265 
266             // construction of the local orbital frame, using PV from reference frame
267             final FieldPVCoordinates<T> pv = pvProv.getPVCoordinates(rawAttitude.getDate(), frame);
268             final Field<T> field = rawAttitude.getDate().getField();
269             final FieldAngularCoordinates<T> referenceToLof =
270                             orf.isQuasiInertial() ?
271                             new FieldAngularCoordinates<>(orf.getLofType().rotationFromInertial(field, pv),
272                                                           FieldVector3D.getZero(field)) :
273                             orf.getLofType().transformFromInertial(att.getDate(), pv).getAngular();
274 
275             // compose with APM
276             return new FieldAttitude<>(frame, att.addOffset(referenceToLof));
277 
278         } else {
279             // this is an absolute attitude
280             if (external.asFrame() == null) {
281                 // this should never happen as all CelestialBodyFrame have an Orekit mapping
282                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_FRAME, external.getName());
283             }
284             final FieldAttitude<T> attitude = new FieldAttitude<>(external.asFrame(), att);
285             return frame == null ? attitude : attitude.withReferenceFrame(frame);
286         }
287 
288     }
289 
290     /** {@inheritDoc} */
291     @Override
292     public String toString() {
293         return frameA.getName() + (isA2b() ? " → " : " ← ") + frameB.getName();
294     }
295 
296 }