1   /* Copyright 2002-2025 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.frames;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
22  import org.hipparchus.geometry.euclidean.threed.Rotation;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.orekit.bodies.OneAxisEllipsoid;
25  import org.orekit.models.earth.GeoMagneticField;
26  import org.orekit.models.earth.ReferenceEllipsoid;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.time.FieldAbsoluteDate;
29  import org.orekit.utils.FieldPVCoordinates;
30  import org.orekit.utils.PVCoordinates;
31  
32  /**
33   * This class handles a magnetic field variation attitude provider.
34   * <p>
35   * It was designed to be used as a Bdot attitude pointing law which align a specific body axis with Earth magnetic field
36   * vector.
37   * <p>
38   * Attitude control thought the magnetic field is called Bdot as it follows the sinusoidal variation of the Earth magnetic
39   * field vector, along the orbit. Magnetorquers are used on board to align the instrument, as so the satellite, with the
40   * planet magnetic field, producing a sinusoidal torque along the orbit.
41   *
42   * @author Alberto Ferrero
43   * @author Vincent Cucchietti
44   */
45  public class LocalMagneticFieldFrame implements LOF {
46  
47      /** Inertial frame in which position-velocity coordinates will be given when computing transform and rotation. */
48      private final Frame inertialFrame;
49  
50      /** Vector used to define the local orbital frame. */
51      private final LOFBuilderVector lofBuilderVector;
52  
53      /** Body shape. */
54      private final OneAxisEllipsoid wgs84BodyShape;
55  
56      /** Earth's magnetic field. */
57      private final GeoMagneticField magneticField;
58  
59      /**
60       * Constructor with default definition of the local orbital frame:
61       * <ul>
62       *     <li> x: Magnetic field</li>
63       *     <li> y: Completes orthonormal frame</li>
64       *     <li> z: Cross product of the magnetic field with the orbital momentum</li>
65       * </ul>.
66       * <b>BEWARE : Do not use this constructor if it is planned to be used with an equatorial orbit as the magnetic field and
67       * orbital momentum vectors will be parallel and cause an error to be thrown</b>
68       *
69       * @param inertialFrame inertial frame in which position-velocity coordinates will be given when computing transform and
70       * rotation
71       * @param magneticField Earth magnetic field model
72       * @param bodyFrame body frame related to body shape
73       */
74      public LocalMagneticFieldFrame(final Frame inertialFrame,
75                                     final GeoMagneticField magneticField,
76                                     final Frame bodyFrame) {
77          this(inertialFrame, magneticField, LOFBuilderVector.PLUS_MOMENTUM, bodyFrame);
78      }
79  
80      /**
81       * Constructor with custom definition of the local orbital frame:
82       * <ul>
83       *     <li> x: Magnetic field</li>
84       *     <li> y: Completes orthonormal frame</li>
85       *     <li> z: Cross product of the magnetic field with chosen {@link LOFBuilderVector vector}</li>
86       * </ul>
87       * For near-polar orbits, it is suggested to use the {@link LOFBuilderVector orbital momentum} to define the local
88       * orbital frame. However, for near-equatorial orbits, it is advised to use either the
89       * {@link LOFBuilderVector position or the velocity}.
90       *
91       * @param inertialFrame inertial frame in which position-velocity coordinates will be given when computing transform and
92       * rotation
93       * @param magneticField Earth magnetic field model
94       * @param lofBuilderVector vector used to define the local orbital frame
95       * @param bodyFrame body frame related to body shape
96       */
97      public LocalMagneticFieldFrame(final Frame inertialFrame, final GeoMagneticField magneticField,
98                                     final LOFBuilderVector lofBuilderVector,
99                                     final Frame bodyFrame) {
100         this.inertialFrame    = inertialFrame;
101         this.magneticField    = magneticField;
102         this.lofBuilderVector = lofBuilderVector;
103         // Default WGS84 body shape as this is the one used by default in GeoMagneticField
104         this.wgs84BodyShape = ReferenceEllipsoid.getWgs84(bodyFrame);
105     }
106 
107     /**
108      * {@inheritDoc} Direction as X axis aligned with magnetic field vector, Y axis aligned with the cross product of the
109      * magnetic field vector with chosen {@link LOFBuilderVector vector type}.
110      * <p>
111      * <b>BEWARE: In this implementation, the method simply fieldify the normal rotation with given field.
112      * Hence all derivatives are lost.</b>
113      */
114     @Override
115     public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
116                                                                                      final FieldAbsoluteDate<T> date,
117                                                                                      final FieldPVCoordinates<T> pv) {
118         // TODO Implement field equivalent of "calculateField" method in GeoMagneticField
119         final Rotation rotation = rotationFromInertial(date.toAbsoluteDate(), pv.toPVCoordinates());
120         return new FieldRotation<>(field, rotation);
121     }
122 
123     /**
124      * {@inheritDoc} Direction as X axis aligned with magnetic field vector, Z axis aligned with the cross product of the
125      * magnetic field vector with chosen {@link LOFBuilderVector vector type}.
126      */
127     @Override
128     public Rotation rotationFromInertial(final AbsoluteDate date, final PVCoordinates pv) {
129         // Express satellite coordinates in body frame
130         final StaticTransform inertialToBodyFrame = inertialFrame.getStaticTransformTo(wgs84BodyShape.getBodyFrame(), date);
131         final Vector3D        posBody             = inertialToBodyFrame.transformPosition(pv.getPosition());
132 
133         // Compute satellite coordinates LLA and magnetic field vector in body frame
134         final double   lat            = posBody.getDelta();
135         final double   lng            = posBody.getAlpha();
136         final double   alt            = posBody.getNorm() - wgs84BodyShape.getEquatorialRadius();
137         final Vector3D magnVectorBody = magneticField.calculateField(lat, lng, alt).getFieldVector();
138 
139         // Compute magnetic field in inertial frame
140         final StaticTransform bodyToInertialFrame = inertialToBodyFrame.getInverse();
141         final Vector3D        magnVector          = bodyToInertialFrame.transformVector(magnVectorBody);
142 
143         return new Rotation(magnVector, magnVector.crossProduct(lofBuilderVector.getVector(pv)),
144                             Vector3D.PLUS_I, Vector3D.PLUS_K);
145     }
146 
147     /** {@inheritDoc} */
148     @Override
149     public String getName() {
150         return "LOCAL_MAGNETIC_FIELD_FRAME";
151     }
152 
153     /** Get interlai frame.
154      * @return inertial frame
155      */
156     public Frame getInertialFrame() {
157         return inertialFrame;
158     }
159 
160     /** Get geomagnetid field.
161      * @return geo magnetic field
162      */
163     public GeoMagneticField getMagneticField() {
164         return magneticField;
165     }
166 
167     /**
168      * Enum defining how the +j axis of the local orbital frame will be defined.
169      * <p>
170      * For example, if {@code MINUS_MOMENTUM} is chosen, +j aligned as the cross product of the momentum vector  and the
171      * magnetic field vector. The resulting body frame will be +x aligned with magnetic field, +z aligned with negative
172      * momentum, +y orthonormal.
173      */
174     public enum LOFBuilderVector {
175 
176         /** Positive position vector. */
177         PLUS_POSITION {
178             /** {@inheritDoc} */
179             @Override
180             Vector3D getVector(final PVCoordinates pv) {
181                 return pv.getPosition();
182             }
183         },
184 
185         /** Positive velocity vector. */
186         PLUS_VELOCITY {
187             /** {@inheritDoc} */
188             @Override
189             Vector3D getVector(final PVCoordinates pv) {
190                 return pv.getVelocity();
191             }
192         },
193 
194         /** Positive orbital momentum vector. */
195         PLUS_MOMENTUM {
196             /** {@inheritDoc} */
197             @Override
198             Vector3D getVector(final PVCoordinates pv) {
199                 return pv.getMomentum();
200             }
201         },
202 
203         /** Negative position vector. */
204         MINUS_POSITION {
205             /** {@inheritDoc} */
206             @Override
207             Vector3D getVector(final PVCoordinates pv) {
208                 return pv.getPosition().negate();
209             }
210         },
211 
212         /** Negative velocity vector. */
213         MINUS_VELOCITY {
214             /** {@inheritDoc} */
215             @Override
216             Vector3D getVector(final PVCoordinates pv) {
217                 return pv.getVelocity().negate();
218             }
219         },
220 
221         /** Negative orbital momentum vector. */
222         MINUS_MOMENTUM {
223             /** {@inheritDoc} */
224             @Override
225             Vector3D getVector(final PVCoordinates pv) {
226                 return pv.getMomentum().negate();
227             }
228         };
229 
230         /**
231          * @param pv position-velocity coordinates expressed in the instance inertial frame
232          *
233          * @return Vector used to define the local orbital frame by computing the cross product of the magnetic field with
234          * this
235          */
236         abstract Vector3D getVector(PVCoordinates pv);
237     }
238 
239 }