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 }