1   /* Copyright 2002-2021 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.bodies;
18  
19  import java.io.Serializable;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.geometry.euclidean.threed.Rotation;
25  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
26  import org.hipparchus.geometry.euclidean.threed.Vector3D;
27  import org.hipparchus.util.Precision;
28  import org.orekit.annotation.DefaultDataContext;
29  import org.orekit.bodies.JPLEphemeridesLoader.EphemerisType;
30  import org.orekit.data.DataContext;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitInternalError;
33  import org.orekit.frames.FieldTransform;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.Transform;
36  import org.orekit.frames.TransformProvider;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.utils.FieldPVCoordinates;
40  import org.orekit.utils.PVCoordinates;
41  import org.orekit.utils.TimeStampedFieldPVCoordinates;
42  import org.orekit.utils.TimeStampedPVCoordinates;
43  
44  /** Implementation of the {@link CelestialBody} interface using JPL or INPOP ephemerides.
45   * @author Luc Maisonobe
46   */
47  class JPLCelestialBody implements CelestialBody {
48  
49      /** Serializable UID. */
50      private static final long serialVersionUID = 3809787672779740923L;
51  
52      /** Name of the body. */
53      private final String name;
54  
55      /** Regular expression for supported files names. */
56      private final String supportedNames;
57  
58      /** Ephemeris type to generate. */
59      private final JPLEphemeridesLoader.EphemerisType generateType;
60  
61      /** Raw position-velocity provider. */
62      private final transient JPLEphemeridesLoader.RawPVProvider rawPVProvider;
63  
64      /** Attraction coefficient of the body (m³/s²). */
65      private final double gm;
66  
67      /** Scaling factor for position-velocity. */
68      private final double scale;
69  
70      /** IAU pole. */
71      private final IAUPole iauPole;
72  
73      /** Inertially oriented, body-centered frame. */
74      private final Frame inertialFrame;
75  
76      /** Body oriented, body-centered frame. */
77      private final Frame bodyFrame;
78  
79      /** Build an instance and the underlying frame.
80       * @param name name of the body
81       * @param supportedNames regular expression for supported files names
82       * @param generateType ephemeris type to generate
83       * @param rawPVProvider raw position-velocity provider
84       * @param gm attraction coefficient (in m³/s²)
85       * @param scale scaling factor for position-velocity
86       * @param iauPole IAU pole implementation
87       * @param definingFrameAlignedWithICRF frame in which celestial body coordinates are defined,
88       * this frame <strong>must</strong> be aligned with ICRF
89       * @param inertialFrameName name to use for inertial frame (if null a default name will be built)
90       * @param bodyOrientedFrameName name to use for body-oriented frame (if null a default name will be built)
91       */
92      JPLCelestialBody(final String name, final String supportedNames,
93                       final JPLEphemeridesLoader.EphemerisType generateType,
94                       final JPLEphemeridesLoader.RawPVProvider rawPVProvider,
95                       final double gm, final double scale,
96                       final IAUPole iauPole, final Frame definingFrameAlignedWithICRF,
97                       final String inertialFrameName, final String bodyOrientedFrameName) {
98          this.name           = name;
99          this.gm             = gm;
100         this.scale          = scale;
101         this.supportedNames = supportedNames;
102         this.generateType   = generateType;
103         this.rawPVProvider  = rawPVProvider;
104         this.iauPole        = iauPole;
105         this.inertialFrame  = new InertiallyOriented(definingFrameAlignedWithICRF, inertialFrameName);
106         this.bodyFrame      = new BodyOriented(bodyOrientedFrameName);
107     }
108 
109     /** {@inheritDoc} */
110     public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
111 
112         // apply the scale factor to raw position-velocity
113         final PVCoordinates rawPV    = rawPVProvider.getRawPV(date);
114         final TimeStampedPVCoordinates scaledPV = new TimeStampedPVCoordinates(date, scale, rawPV);
115 
116         // the raw PV are relative to the parent of the body centered inertially oriented frame
117         final Transform transform = getInertiallyOrientedFrame().getParent().getTransformTo(frame, date);
118 
119         // convert to requested frame
120         return transform.transformPVCoordinates(scaledPV);
121 
122     }
123 
124     /** Get the {@link FieldPVCoordinates} of the body in the selected frame.
125      * @param date current date
126      * @param frame the frame where to define the position
127      * @param <T> type fo the field elements
128      * @return time-stamped position/velocity of the body (m and m/s)
129      */
130     public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date,
131                                                                                              final Frame frame) {
132 
133         // apply the scale factor to raw position-velocity
134         final FieldPVCoordinates<T> rawPV    = rawPVProvider.getRawPV(date);
135         final TimeStampedFieldPVCoordinates<T> scaledPV = new TimeStampedFieldPVCoordinates<>(date, scale, rawPV);
136 
137         // the raw PV are relative to the parent of the body centered inertially oriented frame
138         final FieldTransform<T> transform = getInertiallyOrientedFrame().getParent().getTransformTo(frame, date);
139 
140         // convert to requested frame
141         return transform.transformPVCoordinates(scaledPV);
142 
143     }
144 
145     /** Replace the instance with a data transfer object for serialization.
146      * <p>
147      * This intermediate class serializes the files supported names, the ephemeris type
148      * and the body name.
149      * </p>
150      * @return data transfer object that will be serialized
151      */
152     @DefaultDataContext
153     private Object writeReplace() {
154         return new DTOCelestialBody(supportedNames, generateType, name);
155     }
156 
157     /** {@inheritDoc} */
158     public String getName() {
159         return name;
160     }
161 
162     /** {@inheritDoc} */
163     public double getGM() {
164         return gm;
165     }
166 
167     /** {@inheritDoc} */
168     public Frame getInertiallyOrientedFrame() {
169         return inertialFrame;
170     }
171 
172     /** {@inheritDoc} */
173     public Frame getBodyOrientedFrame() {
174         return bodyFrame;
175     }
176 
177    /** Inertially oriented body centered frame. */
178     private class InertiallyOriented extends Frame {
179 
180         /** Serializable UID. */
181         private static final long serialVersionUID = -8849993808761896559L;
182 
183         /** Suffix for inertial frame name. */
184         private static final String INERTIAL_FRAME_SUFFIX = "/inertial";
185 
186         /** Simple constructor.
187          * @param definingFrame frame in which celestial body coordinates are defined
188          * @param frameName name to use (if null a default name will be built)
189          */
190         InertiallyOriented(final Frame definingFrame, final String frameName) {
191             super(definingFrame, new TransformProvider() {
192 
193                 /** Serializable UID. */
194                 private static final long serialVersionUID = -8610328386110652400L;
195 
196                 /** {@inheritDoc} */
197                 public Transform getTransform(final AbsoluteDate date) {
198 
199                     // compute translation from parent frame to self
200                     final PVCoordinates pv = getPVCoordinates(date, definingFrame);
201                     final Transform translation = new Transform(date, pv.negate());
202 
203                     // compute rotation from ICRF frame to self,
204                     // as per the "Report of the IAU/IAG Working Group on Cartographic
205                     // Coordinates and Rotational Elements of the Planets and Satellites"
206                     // These definitions are common for all recent versions of this report
207                     // published every three years, the precise values of pole direction
208                     // and W angle coefficients may vary from publication year as models are
209                     // adjusted. These coefficients are not in this class, they are in the
210                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
211                     // methods
212                     final Vector3D pole  = iauPole.getPole(date);
213                     final Vector3D qNode = iauPole.getNode(date);
214                     final Transform rotation =
215                             new Transform(date, new Rotation(pole, qNode, Vector3D.PLUS_K, Vector3D.PLUS_I));
216 
217                     // update transform from parent to self
218                     return new Transform(date, translation, rotation);
219 
220                 }
221 
222                 /** {@inheritDoc} */
223                 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
224 
225                     // compute translation from parent frame to self
226                     final FieldPVCoordinates<T> pv = getPVCoordinates(date, definingFrame);
227                     final FieldTransform<T> translation = new FieldTransform<>(date, pv.negate());
228 
229                     // compute rotation from ICRF frame to self,
230                     // as per the "Report of the IAU/IAG Working Group on Cartographic
231                     // Coordinates and Rotational Elements of the Planets and Satellites"
232                     // These definitions are common for all recent versions of this report
233                     // published every three years, the precise values of pole direction
234                     // and W angle coefficients may vary from publication year as models are
235                     // adjusted. These coefficients are not in this class, they are in the
236                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
237                     // methods
238                     final FieldVector3D<T> pole  = iauPole.getPole(date);
239                     FieldVector3D<T> qNode = FieldVector3D.crossProduct(Vector3D.PLUS_K, pole);
240                     if (qNode.getNormSq().getReal() < Precision.SAFE_MIN) {
241                         qNode = FieldVector3D.getPlusI(date.getField());
242                     }
243                     final FieldTransform<T> rotation =
244                             new FieldTransform<>(date,
245                                                  new FieldRotation<>(pole,
246                                                                      qNode,
247                                                                      FieldVector3D.getPlusK(date.getField()),
248                                                                      FieldVector3D.getPlusI(date.getField())));
249 
250                     // update transform from parent to self
251                     return new FieldTransform<>(date, translation, rotation);
252 
253                 }
254 
255             }, frameName == null ? name + INERTIAL_FRAME_SUFFIX : frameName, true);
256         }
257 
258         /** Replace the instance with a data transfer object for serialization.
259          * <p>
260          * This intermediate class serializes the files supported names, the ephemeris type
261          * and the body name.
262          * </p>
263          * @return data transfer object that will be serialized
264          */
265         @DefaultDataContext
266         private Object writeReplace() {
267             return new DTOInertialFrame(supportedNames, generateType, name);
268         }
269 
270     }
271 
272     /** Body oriented body centered frame. */
273     private class BodyOriented extends Frame {
274 
275         /** Serializable UID. */
276         private static final long serialVersionUID = 20170109L;
277 
278         /** Suffix for body frame name. */
279         private static final String BODY_FRAME_SUFFIX = "/rotating";
280 
281         /** Simple constructor.
282          * @param frameName name to use (if null a default name will be built)
283          */
284         BodyOriented(final String frameName) {
285             super(inertialFrame, new TransformProvider() {
286 
287                 /** Serializable UID. */
288                 private static final long serialVersionUID = 20170109L;
289 
290                 /** {@inheritDoc} */
291                 public Transform getTransform(final AbsoluteDate date) {
292                     final double dt = 10.0;
293                     final double w0 = iauPole.getPrimeMeridianAngle(date);
294                     final double w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
295                     return new Transform(date,
296                                          new Rotation(Vector3D.PLUS_K, w0, RotationConvention.FRAME_TRANSFORM),
297                                          new Vector3D((w1 - w0) / dt, Vector3D.PLUS_K));
298                 }
299 
300                 /** {@inheritDoc} */
301                 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
302                     final double dt = 10.0;
303                     final T w0 = iauPole.getPrimeMeridianAngle(date);
304                     final T w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
305                     return new FieldTransform<>(date,
306                                                 new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), w0,
307                                                                     RotationConvention.FRAME_TRANSFORM),
308                                                 new FieldVector3D<>(w1.subtract(w0).divide(dt), Vector3D.PLUS_K));
309                 }
310 
311             }, frameName == null ? name + BODY_FRAME_SUFFIX : frameName, false);
312         }
313 
314         /** Replace the instance with a data transfer object for serialization.
315          * <p>
316          * This intermediate class serializes the files supported names, the ephemeris type
317          * and the body name.
318          * </p>
319          * @return data transfer object that will be serialized
320          */
321         @DefaultDataContext
322         private Object writeReplace() {
323             return new DTOBodyFrame(supportedNames, generateType, name);
324         }
325 
326     }
327 
328     /** Internal class used only for serialization. */
329     @DefaultDataContext
330     private abstract static class DataTransferObject implements Serializable {
331 
332         /** Serializable UID. */
333         private static final long serialVersionUID = 674742836536072422L;
334 
335         /** Regular expression for supported files names. */
336         private final String supportedNames;
337 
338         /** Ephemeris type to generate. */
339         private final EphemerisType generateType;
340 
341         /** Name of the body. */
342         private final String name;
343 
344         /** Simple constructor.
345          * @param supportedNames regular expression for supported files names
346          * @param generateType ephemeris type to generate
347          * @param name name of the body
348          */
349         DataTransferObject(final String supportedNames, final EphemerisType generateType, final String name) {
350             this.supportedNames = supportedNames;
351             this.generateType   = generateType;
352             this.name           = name;
353         }
354 
355         /** Get the body associated with the serialized data.
356          * @return body associated with the serialized data
357          */
358         protected JPLCelestialBody getBody() {
359 
360             try {
361                 // first try to use the factory, in order to avoid building a new instance
362                 // each time we deserialize and have the object properly cached
363                 final CelestialBody factoryProvided =
364                         DataContext.getDefault().getCelestialBodies().getBody(name);
365                 if (factoryProvided instanceof JPLCelestialBody) {
366                     final JPLCelestialBody jplBody = (JPLCelestialBody) factoryProvided;
367                     if (supportedNames.equals(jplBody.supportedNames) && generateType == jplBody.generateType) {
368                         // the factory created exactly the object we needed, just return it
369                         return jplBody;
370                     }
371                 }
372 
373                 // the factory does not return the object we want
374                 // we create a new one from scratch and don't cache it
375                 return (JPLCelestialBody) new JPLEphemeridesLoader(supportedNames, generateType).loadCelestialBody(name);
376 
377             } catch (OrekitException oe) {
378                 throw new OrekitInternalError(oe);
379             }
380 
381         }
382 
383     }
384 
385     /** Specialization of the data transfer object for complete celestial body serialization. */
386     @DefaultDataContext
387     private static class DTOCelestialBody extends DataTransferObject {
388 
389         /** Serializable UID. */
390         private static final long serialVersionUID = -8287341529741045958L;
391 
392         /** Simple constructor.
393          * @param supportedNames regular expression for supported files names
394          * @param generateType ephemeris type to generate
395          * @param name name of the body
396          */
397         DTOCelestialBody(final String supportedNames, final EphemerisType generateType, final String name) {
398             super(supportedNames, generateType, name);
399         }
400 
401         /** Replace the deserialized data transfer object with a {@link JPLCelestialBody}.
402          * @return replacement {@link JPLCelestialBody}
403          */
404         private Object readResolve() {
405             return getBody();
406         }
407 
408     }
409 
410     /** Specialization of the data transfer object for inertially oriented frame serialization. */
411     @DefaultDataContext
412     private static class DTOInertialFrame extends DataTransferObject {
413 
414         /** Serializable UID. */
415         private static final long serialVersionUID = 7915071664444154948L;
416 
417         /** Simple constructor.
418          * @param supportedNames regular expression for supported files names
419          * @param generateType ephemeris type to generate
420          * @param name name of the body
421          */
422         DTOInertialFrame(final String supportedNames, final EphemerisType generateType, final String name) {
423             super(supportedNames, generateType, name);
424         }
425 
426         /** Replace the deserialized data transfer object with a {@link Frame}.
427          * @return replacement {@link Frame}
428          */
429         private Object readResolve() {
430             return getBody().inertialFrame;
431         }
432 
433     }
434 
435     /** Specialization of the data transfer object for body oriented frame serialization. */
436     @DefaultDataContext
437     private static class DTOBodyFrame extends DataTransferObject {
438 
439         /** Serializable UID. */
440         private static final long serialVersionUID = -3194195019557081000L;
441 
442         /** Simple constructor.
443          * @param supportedNames regular expression for supported files names
444          * @param generateType ephemeris type to generate
445          * @param name name of the body
446          */
447         DTOBodyFrame(final String supportedNames, final EphemerisType generateType, final String name) {
448             super(supportedNames, generateType, name);
449         }
450 
451         /** Replace the deserialized data transfer object with a {@link Frame}.
452          * @return replacement {@link Frame}
453          */
454         private Object readResolve() {
455             return getBody().bodyFrame;
456         }
457 
458     }
459 
460 }