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.apm;
18  
19  import java.util.Arrays;
20  
21  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.files.ccsds.ndm.adm.AttitudeEndoints;
25  import org.orekit.files.ccsds.section.CommentsContainer;
26  
27  /**
28   * Container for {@link Euler Euler rotations} entries.
29   * @author Bryan Cazabonne
30   * @since 10.2
31   */
32  public class Euler extends CommentsContainer {
33  
34      /** Endpoints (i.e. frames A, B and their relationship). */
35      private final AttitudeEndoints endpoints;
36  
37      /** Rotation order of the Euler angles. */
38      private RotationOrder eulerRotSeq;
39  
40      /** The frame in which rates are specified. */
41      private Boolean rateFrameIsA;
42  
43      /** Euler angles [rad]. */
44      private double[] rotationAngles;
45  
46      /** Rotation rate [rad/s]. */
47      private double[] rotationRates;
48  
49      /** Indicator for rotation angles. */
50      private boolean inRotationAngles;
51  
52      /** Simple constructor.
53       */
54      public Euler() {
55          this.endpoints        = new AttitudeEndoints();
56          this.rotationAngles   = new double[3];
57          this.rotationRates    = new double[3];
58          this.inRotationAngles = false;
59          Arrays.fill(rotationAngles, Double.NaN);
60          Arrays.fill(rotationRates,  Double.NaN);
61      }
62  
63      /** {@inheritDoc} */
64      @Override
65      public void validate(final double version) {
66  
67          super.validate(version);
68          endpoints.checkMandatoryEntriesExceptExternalFrame(EulerKey.EULER_FRAME_A,
69                                                             EulerKey.EULER_FRAME_B,
70                                                             EulerKey.EULER_DIR);
71          endpoints.checkExternalFrame(EulerKey.EULER_FRAME_A, EulerKey.EULER_FRAME_B);
72          checkNotNull(eulerRotSeq, EulerKey.EULER_ROT_SEQ);
73  
74          final boolean missingAngle = Double.isNaN(rotationAngles[0] + rotationAngles[1] + rotationAngles[2]);
75          if (missingAngle) {
76              // if at least one is NaN, all must be NaN (i.e. not initialized)
77              for (final double ra : rotationAngles) {
78                  if (!Double.isNaN(ra)) {
79                      throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, "{X|Y|Z}_ANGLE");
80                  }
81              }
82          }
83  
84          final boolean missingRate = Double.isNaN(rotationRates[0] + rotationRates[1] + rotationRates[2]);
85          if (missingRate) {
86              // if at least one is NaN, all must be NaN (i.e. not initialized)
87              for (final double rr : rotationRates) {
88                  if (!Double.isNaN(rr)) {
89                      throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, "{X|Y|Z}_RATE");
90                  }
91              }
92          }
93  
94          // either angles or rates must be specified
95          // (angles may be missing in the quaternion/Euler rate case)
96          if (missingAngle && missingRate) {
97              throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, "{X|Y|Z}_{ANGLE|RATE}");
98          }
99  
100     }
101 
102     /** Get the endpoints (i.e. frames A, B and their relationship).
103      * @return endpoints
104      */
105     public AttitudeEndoints getEndpoints() {
106         return endpoints;
107     }
108 
109     /**
110      * Get the rotation order of Euler angles.
111      * @return rotation order
112      */
113     public RotationOrder getEulerRotSeq() {
114         return eulerRotSeq;
115     }
116 
117     /**
118      * Set the rotation order for Euler angles.
119      * @param eulerRotSeq order to be set
120      */
121     public void setEulerRotSeq(final RotationOrder eulerRotSeq) {
122         refuseFurtherComments();
123         this.eulerRotSeq = eulerRotSeq;
124     }
125 
126     /** Check if rates are specified in {@link AttitudeEndoints#getFrameA() frame A}.
127      * @return true if rates are specified in {@link AttitudeEndoints#getFrameA() frame A}
128      */
129     public boolean rateFrameIsA() {
130         return rateFrameIsA == null ? false : rateFrameIsA;
131     }
132 
133     /** Set the frame in which rates are specified.
134      * @param rateFrameIsA if true, rates are specified in {@link AttitudeEndoints#getFrameA() frame A}
135      */
136     public void setRateFrameIsA(final boolean rateFrameIsA) {
137         refuseFurtherComments();
138         this.rateFrameIsA = rateFrameIsA;
139     }
140 
141     /** Check if rates are specified in spacecraft body frame.
142      * <p>
143      * {@link #validate(double) Mandatory entries} must have been
144      * initialized properly to non-null values before this method is called,
145      * otherwise {@code NullPointerException} will be thrown.
146      * </p>
147      * @return true if rates are specified in spacecraft body frame
148      */
149     public boolean isSpacecraftBodyRate() {
150         return rateFrameIsA ^ endpoints.getFrameA().asSpacecraftBodyFrame() == null;
151     }
152 
153     /**
154      * Get the coordinates of the Euler angles (rad).
155      * @return rotation angles
156      */
157     public double[] getRotationAngles() {
158         return rotationAngles.clone();
159     }
160 
161     /**
162      * Set the Euler angle about (rad).
163      * @param axis rotation axis
164      * @param angle angle to set
165      */
166     public void setRotationAngle(final char axis, final double angle) {
167         refuseFurtherComments();
168         setAngleOrRate(rotationAngles, axis, angle);
169     }
170 
171     /**
172      * Get the rates of the Euler angles (rad/s).
173      * @return rotation rates
174      */
175     public double[] getRotationRates() {
176         return rotationRates.clone();
177     }
178 
179     /**
180      * Set the rate of Euler angle (rad/s).
181      * @param axis rotation axis
182      * @param rate angle rate to set
183      */
184     public void setRotationRate(final char axis, final double rate) {
185         refuseFurtherComments();
186         setAngleOrRate(rotationRates, axis, rate);
187     }
188 
189     /** Check if we are in the rotationAngles part of XML files.
190      * @return true if we are in the rotationAngles part of XML files
191      */
192     boolean inRotationAngles() {
193         return inRotationAngles;
194     }
195 
196     /** Set flag for rotation angle parsing.
197      * @param inRotationAngles if true, we are in the rotationAngles part of XML files
198      */
199     public void setInRotationAngles(final boolean inRotationAngles) {
200         refuseFurtherComments();
201         this.inRotationAngles = inRotationAngles;
202     }
203 
204     /** Check if the logical block includes rates.
205      * @return true if logical block includes rates
206      */
207     public boolean hasRates() {
208         return !Double.isNaN(rotationRates[0] + rotationRates[1] + rotationRates[2]);
209     }
210 
211     /** Set an angle or rate in an array.
212      * @param array angle or rate array
213      * @param axis axis name
214      * @param value angle or rate to set
215      */
216     private void setAngleOrRate(final double[] array, final char axis, final double value) {
217         refuseFurtherComments();
218         if (eulerRotSeq != null) {
219             if (eulerRotSeq.name().charAt(0) == axis && Double.isNaN(array[0])) {
220                 array[0] = value;
221             } else if (eulerRotSeq.name().charAt(1) == axis && Double.isNaN(array[1])) {
222                 array[1] = value;
223             } else if (eulerRotSeq.name().charAt(2) == axis && Double.isNaN(array[2])) {
224                 array[2] = value;
225             }
226         }
227     }
228 }