1   /* Copyright 2022-2026 Luc Maisonobe
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  
18  package org.orekit.files.ccsds.ndm.adm.acm;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.files.ccsds.definitions.AdMethodType;
28  import org.orekit.files.ccsds.definitions.CcsdsFrameMapper;
29  import org.orekit.files.ccsds.ndm.adm.AttitudeEndpoints;
30  import org.orekit.files.ccsds.section.CommentsContainer;
31  import org.orekit.frames.Frame;
32  
33  /** Attitude determination data.
34   * <p>
35   * Beware that the Orekit getters and setters all rely on SI units. The parsers
36   * and writers take care of converting these SI units into CCSDS mandatory units.
37   * The {@link org.orekit.utils.units.Unit Unit} class provides useful
38   * {@link org.orekit.utils.units.Unit#fromSI(double) fromSi} and
39   * {@link org.orekit.utils.units.Unit#toSI(double) toSI} methods in case the callers
40   * already use CCSDS units instead of the API SI units. The general-purpose
41   * {@link org.orekit.utils.units.Unit Unit} class (without an 's') and the
42   * CCSDS-specific {@link org.orekit.files.ccsds.definitions.Units Units} class
43   * (with an 's') also provide some predefined units. These predefined units and the
44   * {@link org.orekit.utils.units.Unit#fromSI(double) fromSi} and
45   * {@link org.orekit.utils.units.Unit#toSI(double) toSI} conversion methods are indeed
46   * what the parsers and writers use for the conversions.
47   * </p>
48   * @author Luc Maisonobe
49   * @since 12.0
50   */
51  public class AttitudeDetermination extends CommentsContainer {
52  
53      /** Endpoints (i.e. frames A, B and their relationship). */
54      private final AttitudeEndpoints endpoints;
55  
56      /** Identification number. */
57      private String id;
58  
59      /** Identification of previous orbit determination. */
60      private String prevId;
61  
62      /** Attitude determination method. */
63      private AdMethodType method;
64  
65      /** Source of attitude estimate. */
66      private String source;
67  
68      /** Rotation order for Euler angles. */
69      private RotationOrder eulerRotSeq;
70  
71      /** Number of states for {@link AdMethodType#EKF}, {@link AdMethodType#BATCH} or {@link AdMethodType#FILTER_SMOOTHER}. */
72      private int nbStates;
73  
74      /** Attitude states. */
75      private AttitudeElementsType attitudeStates;
76  
77      /** Type of attitude error state. */
78      private AttitudeCovarianceType covarianceType;
79  
80      /** Attitude rate states. */
81      private RateElementsType rateStates;
82  
83      /** Rate random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}. */
84      private double sigmaU;
85  
86      /** Angle random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}. */
87      private double sigmaV;
88  
89      /** Process noise standard deviation if {@link #rateStates} is {@link RateElementsType#ANGVEL}. */
90      private double rateProcessNoiseStdDev;
91  
92      /** Sensors used. */
93      private final List<AttitudeDeterminationSensor> sensorsUsed;
94  
95      /**
96       * Simple constructor.
97       *
98       * @param frameMapper for creating a {@link Frame}.
99       * @since 13.1.5
100      */
101     public AttitudeDetermination(final CcsdsFrameMapper frameMapper) {
102         endpoints   = new AttitudeEndpoints(frameMapper);
103         sensorsUsed = new ArrayList<>();
104         nbStates    = -1;
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public void validate(final double version) {
110         super.validate(version);
111         checkNotNull(attitudeStates, AttitudeDeterminationKey.ATTITUDE_STATES.name());
112         endpoints.checkExternalFrame(AttitudeDeterminationKey.REF_FRAME_A,
113                                      AttitudeDeterminationKey.REF_FRAME_B);
114 
115         // check sensors in increasing number
116         for (int number = 1; number <= sensorsUsed.size(); ++number) {
117             final AttitudeDeterminationSensor sensor = findSensor(number);
118             if (sensor != null) {
119                 sensor.validate(version);
120             } else {
121                 // no sensor has the expected index
122                 throw new OrekitException(OrekitMessages.CCSDS_MISSING_SENSOR_INDEX, number);
123             }
124 
125         }
126 
127     }
128 
129     /** Find sensor by number.
130      * @param number number of the sensor
131      * @return sensor with specified number, or null if not found
132      */
133     private AttitudeDeterminationSensor findSensor(final int number) {
134         for (final AttitudeDeterminationSensor sensor : sensorsUsed) {
135             if (sensor.getSensorNumber() == number) {
136                 return sensor;
137             }
138         }
139         return null;
140     }
141 
142     /** Get the endpoints (i.e. frames A, B and their relationship).
143      * @return endpoints
144      */
145     public AttitudeEndpoints getEndpoints() {
146         return endpoints;
147     }
148 
149     /** Get identification number.
150      * @return identification number
151      */
152     public String getId() {
153         return id;
154     }
155 
156     /** Set identification number.
157      * @param id identification number
158      */
159     public void setId(final String id) {
160         this.id = id;
161     }
162 
163     /** Get identification of previous orbit determination.
164      * @return identification of previous orbit determination
165      */
166     public String getPrevId() {
167         return prevId;
168     }
169 
170     /** Set identification of previous orbit determination.
171      * @param prevId identification of previous orbit determination
172      */
173     public void setPrevId(final String prevId) {
174         this.prevId = prevId;
175     }
176 
177     /** Get attitude determination method.
178      * @return attitude determination method
179      */
180     public AdMethodType getMethod() {
181         return method;
182     }
183 
184     /** Set attitude determination method.
185      * @param method attitude determination method
186      */
187     public void setMethod(final AdMethodType method) {
188         this.method = method;
189     }
190 
191     /** Get source of attitude estimate.
192      * @return source of attitude estimate
193      */
194     public String getSource() {
195         return source;
196     }
197 
198     /** Set source of attitude estimate.
199      * @param source source of attitude estimate
200      */
201     public void setSource(final String source) {
202         this.source = source;
203     }
204 
205     /** Get the rotation order for Euler angles.
206      * @return rotation order for Euler angles
207      */
208     public RotationOrder getEulerRotSeq() {
209         return eulerRotSeq;
210     }
211 
212     /** Set the rotation order for Euler angles.
213      * @param eulerRotSeq rotation order for Euler angles
214      */
215     public void setEulerRotSeq(final RotationOrder eulerRotSeq) {
216         this.eulerRotSeq = eulerRotSeq;
217     }
218 
219     /** Get number of states for {@link AdMethodType#EKF}, {@link AdMethodType#BATCH} or {@link AdMethodType#FILTER_SMOOTHER}.
220      * @return number of states
221      */
222     public int getNbStates() {
223         return nbStates;
224     }
225 
226     /** Set number of states for {@link AdMethodType#EKF}, {@link AdMethodType#BATCH} or {@link AdMethodType#FILTER_SMOOTHER}.
227      * @param nbStates number of states
228      */
229     public void setNbStates(final int nbStates) {
230         this.nbStates = nbStates;
231     }
232 
233     /** Get attitude states.
234      * @return attitude states
235      */
236     public AttitudeElementsType getAttitudeStates() {
237         return attitudeStates;
238     }
239 
240     /** Set attitude states.
241      * @param attitudeStates attitude states
242      */
243     public void setAttitudeStates(final AttitudeElementsType attitudeStates) {
244         this.attitudeStates = attitudeStates;
245     }
246 
247     /** Get type of attitude error state.
248      * @return type of attitude error state
249      */
250     public AttitudeCovarianceType getCovarianceType() {
251         return covarianceType;
252     }
253 
254     /** Set type of attitude error state.
255      * @param covarianceType type of attitude error state
256      */
257     public void setCovarianceType(final AttitudeCovarianceType covarianceType) {
258         this.covarianceType = covarianceType;
259     }
260 
261     /** Get attitude rate states.
262      * @return attitude rate states
263      */
264     public RateElementsType getRateStates() {
265         return rateStates;
266     }
267 
268     /** Set attitude rate states.
269      * @param rateStates attitude rate states
270      */
271     public void setRateStates(final RateElementsType rateStates) {
272         this.rateStates = rateStates;
273     }
274 
275     /** Get rate random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}.
276      * @return rate random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}
277      */
278     public double getSigmaU() {
279         return sigmaU;
280     }
281 
282     /** Set rate random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}.
283      * @param sigmaU rate random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}
284      */
285     public void setSigmaU(final double sigmaU) {
286         this.sigmaU = sigmaU;
287     }
288 
289     /** Get angle random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}.
290      * @return angle random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}
291      */
292     public double getSigmaV() {
293         return sigmaV;
294     }
295 
296     /** Set angle random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}.
297      * @param sigmaV angle random walk if {@link #rateStates} is {@link RateElementsType#GYRO_BIAS}
298      */
299     public void setSigmaV(final double sigmaV) {
300         this.sigmaV = sigmaV;
301     }
302 
303     /** Get process noise standard deviation if {@link #rateStates} is {@link RateElementsType#ANGVEL}.
304      * @return process noise standard deviation if {@link #rateStates} is {@link RateElementsType#ANGVEL}
305      */
306     public double getRateProcessNoiseStdDev() {
307         return rateProcessNoiseStdDev;
308     }
309 
310     /** Set process noise standard deviation if {@link #rateStates} is {@link RateElementsType#ANGVEL}.
311      * @param rateProcessNoiseStdDev process noise standard deviation if {@link #rateStates} is {@link RateElementsType#ANGVEL}
312      */
313     public void setRateProcessNoiseStdDev(final double rateProcessNoiseStdDev) {
314         this.rateProcessNoiseStdDev = rateProcessNoiseStdDev;
315     }
316 
317     /** Get sensors used.
318      * @return sensors used
319      */
320     public List<AttitudeDeterminationSensor> getSensorsUsed() {
321         return Collections.unmodifiableList(sensorsUsed);
322     }
323 
324     /** Add a sensor used.
325      * @param sensor sensor to add
326      */
327     public void addSensor(final AttitudeDeterminationSensor sensor) {
328         for (final AttitudeDeterminationSensor existing : sensorsUsed) {
329             if (sensor.getSensorNumber() == existing.getSensorNumber()) {
330                 throw new OrekitException(OrekitMessages.CCSDS_SENSOR_INDEX_ALREADY_USED, sensor.getSensorNumber());
331             }
332         }
333         sensorsUsed.add(sensor);
334     }
335 
336 }