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