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