1   /* Copyright 2002-2024 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.attitudes;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.hipparchus.CalculusFieldElement;
23  import org.hipparchus.geometry.euclidean.threed.FieldLine;
24  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25  import org.hipparchus.geometry.euclidean.threed.Line;
26  import org.hipparchus.geometry.euclidean.threed.Vector3D;
27  import org.hipparchus.geometry.euclidean.threed.Rotation;
28  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
29  import org.orekit.bodies.BodyShape;
30  import org.orekit.bodies.FieldGeodeticPoint;
31  import org.orekit.bodies.GeodeticPoint;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  import org.orekit.frames.FieldStaticTransform;
35  import org.orekit.frames.FieldTransform;
36  import org.orekit.frames.Frame;
37  import org.orekit.frames.StaticTransform;
38  import org.orekit.frames.Transform;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.FieldAbsoluteDate;
41  import org.orekit.time.FieldTimeInterpolator;
42  import org.orekit.time.TimeInterpolator;
43  import org.orekit.utils.CartesianDerivativesFilter;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.FieldPVCoordinatesProvider;
46  import org.orekit.utils.PVCoordinatesProvider;
47  import org.orekit.utils.TimeStampedFieldPVCoordinates;
48  import org.orekit.utils.TimeStampedFieldPVCoordinatesHermiteInterpolator;
49  import org.orekit.utils.TimeStampedPVCoordinates;
50  import org.orekit.utils.TimeStampedPVCoordinatesHermiteInterpolator;
51  
52  /**
53   * This class provides a default attitude provider.
54  
55   * <p>
56   * The attitude pointing law is defined by an attitude provider and
57   * the satellite axis vector chosen for pointing.
58   * </p>
59   * @author V&eacute;ronique Pommier-Maurussane
60   */
61  public class LofOffsetPointing extends GroundPointing {
62  
63      /** Rotation from local orbital frame. */
64      private final AttitudeProvider attitudeLaw;
65  
66      /** Body shape. */
67      private final BodyShape shape;
68  
69      /** Chosen satellite axis for pointing, given in satellite frame. */
70      private final Vector3D satPointingVector;
71  
72      /** Creates new instance.
73       * @param inertialFrame frame in which orbital velocities are computed
74       * @param shape Body shape
75       * @param attLaw Attitude law
76       * @param satPointingVector satellite vector defining the pointing direction
77       * @since 7.1
78       */
79      public LofOffsetPointing(final Frame inertialFrame, final BodyShape shape,
80                               final AttitudeProvider attLaw, final Vector3D satPointingVector) {
81          super(inertialFrame, shape.getBodyFrame());
82          this.shape = shape;
83          this.attitudeLaw = attLaw;
84          this.satPointingVector = satPointingVector;
85      }
86  
87      /** {@inheritDoc} */
88      @Override
89      public Attitude getAttitude(final PVCoordinatesProvider pvProv,
90                                  final AbsoluteDate date, final Frame frame) {
91          return attitudeLaw.getAttitude(pvProv, date, frame);
92      }
93  
94      /** {@inheritDoc} */
95      @Override
96      public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
97                                                                              final FieldAbsoluteDate<T> date, final Frame frame) {
98          return attitudeLaw.getAttitude(pvProv, date, frame);
99      }
100 
101     /** {@inheritDoc} */
102     @Override
103     public Rotation getAttitudeRotation(final PVCoordinatesProvider pvProv,
104                                         final AbsoluteDate date, final Frame frame) {
105         return attitudeLaw.getAttitudeRotation(pvProv, date, frame);
106     }
107 
108     /** {@inheritDoc} */
109     @Override
110     public <T extends CalculusFieldElement<T>> FieldRotation<T> getAttitudeRotation(final FieldPVCoordinatesProvider<T> pvProv,
111                                                                                     final FieldAbsoluteDate<T> date, final Frame frame) {
112         return attitudeLaw.getAttitudeRotation(pvProv, date, frame);
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     public TimeStampedPVCoordinates getTargetPV(final PVCoordinatesProvider pvProv,
118                                                 final AbsoluteDate date, final Frame frame) {
119 
120         // sample intersection points in current date neighborhood
121         final double h  = 0.1;
122         final List<TimeStampedPVCoordinates> sample = new ArrayList<>();
123         Transform centralRefToBody = null;
124         for (int i = -1; i < 2; ++i) {
125 
126             final AbsoluteDate shifted = date.shiftedBy(i * h);
127 
128             // transform from specified reference frame to spacecraft frame
129             final StaticTransform refToSc = StaticTransform.of(shifted, pvProv.getPosition(shifted, frame).negate(),
130                 attitudeLaw.getAttitudeRotation(pvProv, shifted, frame));
131 
132             // transform from specified reference frame to body frame
133             final StaticTransform refToBody;
134             if (i == 0) {
135                 refToBody = centralRefToBody = frame.getTransformTo(shape.getBodyFrame(), shifted);
136             } else {
137                 refToBody = frame.getStaticTransformTo(shape.getBodyFrame(), shifted);
138             }
139 
140             sample.add(losIntersectionWithBody(StaticTransform.compose(shifted, refToSc.getInverse(), refToBody)));
141 
142         }
143 
144         // create interpolator
145         final TimeInterpolator<TimeStampedPVCoordinates> interpolator =
146                 new TimeStampedPVCoordinatesHermiteInterpolator(sample.size(), CartesianDerivativesFilter.USE_P);
147 
148         // use interpolation to compute properly the time-derivatives
149         final TimeStampedPVCoordinates targetBody =
150                 interpolator.interpolate(date, sample);
151 
152         // convert back to caller specified frame
153         return centralRefToBody.getInverse().transformPVCoordinates(targetBody);
154 
155     }
156 
157     /** {@inheritDoc} */
158     @Override
159     protected Vector3D getTargetPosition(final PVCoordinatesProvider pvProv, final AbsoluteDate date, final Frame frame) {
160 
161         // transform from specified reference frame to spacecraft frame
162         final StaticTransform refToSc = StaticTransform.of(date, pvProv.getPosition(date, frame).negate(),
163             attitudeLaw.getAttitudeRotation(pvProv, date, frame));
164 
165         // transform from specified reference frame to body frame
166         final StaticTransform refToBody = frame.getStaticTransformTo(shape.getBodyFrame(), date);
167         final Vector3D targetBody = losIntersectionWithBody(StaticTransform.compose(date, refToSc.getInverse(), refToBody)).getPosition();
168 
169         // convert back to caller specified frame
170         return refToBody.getInverse().transformPosition(targetBody);
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getTargetPV(final FieldPVCoordinatesProvider<T> pvProv,
176                                                                                             final FieldAbsoluteDate<T> date,
177                                                                                             final Frame frame) {
178 
179         // sample intersection points in current date neighborhood
180         final double h  = 0.1;
181         final List<TimeStampedFieldPVCoordinates<T>> sample = new ArrayList<>();
182         FieldTransform<T> centralRefToBody = null;
183         for (int i = -1; i < 2; ++i) {
184 
185             final FieldAbsoluteDate<T> shifted = date.shiftedBy(i * h);
186 
187             // transform from specified reference frame to spacecraft frame
188             final FieldStaticTransform<T> refToSc = FieldStaticTransform.of(shifted,
189                 pvProv.getPVCoordinates(shifted, frame).getPosition().negate(), attitudeLaw.getAttitudeRotation(pvProv, shifted, frame));
190 
191             // transform from specified reference frame to body frame
192             final FieldStaticTransform<T> refToBody;
193             if (i == 0) {
194                 refToBody = centralRefToBody = frame.getTransformTo(shape.getBodyFrame(), shifted);
195             } else {
196                 refToBody = frame.getStaticTransformTo(shape.getBodyFrame(), shifted);
197             }
198 
199             sample.add(losIntersectionWithBody(FieldStaticTransform.compose(shifted, refToSc.getInverse(), refToBody)));
200 
201         }
202 
203         // create interpolator
204         final FieldTimeInterpolator<TimeStampedFieldPVCoordinates<T>, T> interpolator =
205                 new TimeStampedFieldPVCoordinatesHermiteInterpolator<>(sample.size(), CartesianDerivativesFilter.USE_P);
206 
207         // use interpolation to compute properly the time-derivatives
208         final TimeStampedFieldPVCoordinates<T> targetBody =
209                 interpolator.interpolate(date, sample);
210 
211         // convert back to caller specified frame
212         return centralRefToBody.getInverse().transformPVCoordinates(targetBody);
213 
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     protected <T extends CalculusFieldElement<T>> FieldVector3D<T> getTargetPosition(final FieldPVCoordinatesProvider<T> pvProv,
219                                                                                      final FieldAbsoluteDate<T> date,
220                                                                                      final Frame frame) {
221 
222         // transform from specified reference frame to spacecraft frame
223         final FieldStaticTransform<T> refToSc = FieldStaticTransform.of(date, pvProv.getPosition(date, frame).negate(),
224                 attitudeLaw.getAttitudeRotation(pvProv, date, frame));
225 
226         // transform from specified reference frame to body frame
227         final FieldStaticTransform<T> refToBody = frame.getStaticTransformTo(shape.getBodyFrame(), date);
228         final FieldVector3D<T> targetBody = losIntersectionWithBody(FieldStaticTransform.compose(date, refToSc.getInverse(), refToBody)).getPosition();
229 
230         // convert back to caller specified frame
231         return refToBody.getInverse().transformPosition(targetBody);
232     }
233 
234     /** Compute line of sight intersection with body.
235      * @param scToBody transform from spacecraft frame to body frame
236      * @return intersection point in body frame (only the position is set!)
237      */
238     private TimeStampedPVCoordinates losIntersectionWithBody(final StaticTransform scToBody) {
239 
240         // compute satellite pointing axis and position/velocity in body frame
241         final Vector3D pointingBodyFrame = scToBody.transformVector(satPointingVector);
242         final Vector3D pBodyFrame        = scToBody.transformPosition(Vector3D.ZERO);
243 
244         // Line from satellite following pointing direction
245         // we use arbitrarily the Earth radius as a scaling factor, it could be anything else
246         final Line pointingLine = new Line(pBodyFrame,
247                                            pBodyFrame.add(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
248                                                           pointingBodyFrame),
249                                            1.0e-10);
250 
251         // Intersection with body shape
252         final GeodeticPoint gpIntersection =
253             shape.getIntersectionPoint(pointingLine, pBodyFrame, shape.getBodyFrame(), scToBody.getDate());
254         final Vector3D pIntersection =
255             (gpIntersection == null) ? null : shape.transform(gpIntersection);
256 
257         // Check there is an intersection and it is not in the reverse pointing direction
258         if (pIntersection == null ||
259             Vector3D.dotProduct(pIntersection.subtract(pBodyFrame), pointingBodyFrame) < 0) {
260             throw new OrekitException(OrekitMessages.ATTITUDE_POINTING_LAW_DOES_NOT_POINT_TO_GROUND);
261         }
262 
263         return new TimeStampedPVCoordinates(scToBody.getDate(),
264                                             pIntersection, Vector3D.ZERO, Vector3D.ZERO);
265 
266     }
267 
268     /** Compute line of sight intersection with body.
269      * @param scToBody transform from spacecraft frame to body frame
270      * @param <T> type of the field elements
271      * @return intersection point in body frame (only the position is set!)
272      */
273     private <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> losIntersectionWithBody(final FieldStaticTransform<T> scToBody) {
274 
275         // compute satellite pointing axis and position/velocity in body frame
276         final FieldVector3D<T> pointingBodyFrame = scToBody.transformVector(satPointingVector);
277         final FieldVector3D<T> pBodyFrame        = scToBody.transformPosition(Vector3D.ZERO);
278 
279         // Line from satellite following pointing direction
280         // we use arbitrarily the Earth radius as a scaling factor, it could be anything else
281         final FieldLine<T> pointingLine = new FieldLine<>(pBodyFrame,
282                                                           pBodyFrame.add(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
283                                                                          pointingBodyFrame),
284                                                           1.0e-10);
285 
286         // Intersection with body shape
287         final FieldGeodeticPoint<T> gpIntersection =
288             shape.getIntersectionPoint(pointingLine, pBodyFrame, shape.getBodyFrame(), new FieldAbsoluteDate<>(pBodyFrame.getX().getField(), scToBody.getDate()));
289         final FieldVector3D<T> pIntersection =
290             (gpIntersection == null) ? null : shape.transform(gpIntersection);
291 
292         // Check there is an intersection and it is not in the reverse pointing direction
293         if (pIntersection == null ||
294             FieldVector3D.dotProduct(pIntersection.subtract(pBodyFrame), pointingBodyFrame).getReal() < 0) {
295             throw new OrekitException(OrekitMessages.ATTITUDE_POINTING_LAW_DOES_NOT_POINT_TO_GROUND);
296         }
297 
298         final FieldVector3D<T> zero = FieldVector3D.getZero(pBodyFrame.getX().getField());
299         return new TimeStampedFieldPVCoordinates<>(scToBody.getDate(),
300                                                    pIntersection, zero, zero);
301 
302     }
303 
304 }