JPLCelestialBody.java

  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. import java.io.Serializable;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  21. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  22. import org.hipparchus.geometry.euclidean.threed.Rotation;
  23. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  25. import org.hipparchus.util.Precision;
  26. import org.orekit.annotation.DefaultDataContext;
  27. import org.orekit.bodies.JPLEphemeridesLoader.EphemerisType;
  28. import org.orekit.data.DataContext;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitInternalError;
  31. import org.orekit.frames.FieldTransform;
  32. import org.orekit.frames.Frame;
  33. import org.orekit.frames.StaticTransform;
  34. import org.orekit.frames.Transform;
  35. import org.orekit.frames.TransformProvider;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.FieldAbsoluteDate;
  38. import org.orekit.utils.FieldPVCoordinates;
  39. import org.orekit.utils.PVCoordinates;
  40. import org.orekit.utils.TimeStampedFieldPVCoordinates;
  41. import org.orekit.utils.TimeStampedPVCoordinates;

  42. /** Implementation of the {@link CelestialBody} interface using JPL or INPOP ephemerides.
  43.  * @author Luc Maisonobe
  44.  */
  45. class JPLCelestialBody implements CelestialBody {

  46.     /** Serializable UID. */
  47.     private static final long serialVersionUID = 3809787672779740923L;

  48.     /** Name of the body. */
  49.     private final String name;

  50.     /** Regular expression for supported files names. */
  51.     private final String supportedNames;

  52.     /** Ephemeris type to generate. */
  53.     private final JPLEphemeridesLoader.EphemerisType generateType;

  54.     /** Raw position-velocity provider. */
  55.     private final transient JPLEphemeridesLoader.RawPVProvider rawPVProvider;

  56.     /** Attraction coefficient of the body (m³/s²). */
  57.     private final double gm;

  58.     /** Scaling factor for position-velocity. */
  59.     private final double scale;

  60.     /** IAU pole. */
  61.     private final IAUPole iauPole;

  62.     /** Inertially oriented, body-centered frame. */
  63.     private final Frame inertialFrame;

  64.     /** Body oriented, body-centered frame. */
  65.     private final Frame bodyFrame;

  66.     /** Build an instance and the underlying frame.
  67.      * @param name name of the body
  68.      * @param supportedNames regular expression for supported files names
  69.      * @param generateType ephemeris type to generate
  70.      * @param rawPVProvider raw position-velocity provider
  71.      * @param gm attraction coefficient (in m³/s²)
  72.      * @param scale scaling factor for position-velocity
  73.      * @param iauPole IAU pole implementation
  74.      * @param definingFrameAlignedWithICRF frame in which celestial body coordinates are defined,
  75.      * this frame <strong>must</strong> be aligned with ICRF
  76.      * @param inertialFrameName name to use for inertial frame (if null a default name will be built)
  77.      * @param bodyOrientedFrameName name to use for body-oriented frame (if null a default name will be built)
  78.      */
  79.     JPLCelestialBody(final String name, final String supportedNames,
  80.                      final JPLEphemeridesLoader.EphemerisType generateType,
  81.                      final JPLEphemeridesLoader.RawPVProvider rawPVProvider,
  82.                      final double gm, final double scale,
  83.                      final IAUPole iauPole, final Frame definingFrameAlignedWithICRF,
  84.                      final String inertialFrameName, final String bodyOrientedFrameName) {
  85.         this.name           = name;
  86.         this.gm             = gm;
  87.         this.scale          = scale;
  88.         this.supportedNames = supportedNames;
  89.         this.generateType   = generateType;
  90.         this.rawPVProvider  = rawPVProvider;
  91.         this.iauPole        = iauPole;
  92.         this.inertialFrame  = new InertiallyOriented(definingFrameAlignedWithICRF, inertialFrameName);
  93.         this.bodyFrame      = new BodyOriented(bodyOrientedFrameName);
  94.     }

  95.     /** {@inheritDoc} */
  96.     public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {

  97.         // apply the scale factor to raw position-velocity
  98.         final PVCoordinates rawPV    = rawPVProvider.getRawPV(date);
  99.         final TimeStampedPVCoordinates scaledPV = new TimeStampedPVCoordinates(date, scale, rawPV);

  100.         // the raw PV are relative to the parent of the body centered inertially oriented frame
  101.         final Transform transform = getInertiallyOrientedFrame().getParent().getTransformTo(frame, date);

  102.         // convert to requested frame
  103.         return transform.transformPVCoordinates(scaledPV);

  104.     }

  105.     /** Get the {@link FieldPVCoordinates} of the body in the selected frame.
  106.      * @param date current date
  107.      * @param frame the frame where to define the position
  108.      * @param <T> type fo the field elements
  109.      * @return time-stamped position/velocity of the body (m and m/s)
  110.      */
  111.     public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date,
  112.                                                                                              final Frame frame) {

  113.         // apply the scale factor to raw position-velocity
  114.         final FieldPVCoordinates<T> rawPV    = rawPVProvider.getRawPV(date);
  115.         final TimeStampedFieldPVCoordinates<T> scaledPV = new TimeStampedFieldPVCoordinates<>(date, scale, rawPV);

  116.         // the raw PV are relative to the parent of the body centered inertially oriented frame
  117.         final FieldTransform<T> transform = getInertiallyOrientedFrame().getParent().getTransformTo(frame, date);

  118.         // convert to requested frame
  119.         return transform.transformPVCoordinates(scaledPV);

  120.     }

  121.     /** Replace the instance with a data transfer object for serialization.
  122.      * <p>
  123.      * This intermediate class serializes the files supported names, the ephemeris type
  124.      * and the body name.
  125.      * </p>
  126.      * @return data transfer object that will be serialized
  127.      */
  128.     @DefaultDataContext
  129.     private Object writeReplace() {
  130.         return new DTOCelestialBody(supportedNames, generateType, name);
  131.     }

  132.     /** {@inheritDoc} */
  133.     public String getName() {
  134.         return name;
  135.     }

  136.     /** {@inheritDoc} */
  137.     public double getGM() {
  138.         return gm;
  139.     }

  140.     /** {@inheritDoc} */
  141.     public Frame getInertiallyOrientedFrame() {
  142.         return inertialFrame;
  143.     }

  144.     /** {@inheritDoc} */
  145.     public Frame getBodyOrientedFrame() {
  146.         return bodyFrame;
  147.     }

  148.    /** Inertially oriented body centered frame. */
  149.     private class InertiallyOriented extends Frame {

  150.         /** Serializable UID. */
  151.         private static final long serialVersionUID = -8849993808761896559L;

  152.         /** Suffix for inertial frame name. */
  153.         private static final String INERTIAL_FRAME_SUFFIX = "/inertial";

  154.         /** Simple constructor.
  155.          * @param definingFrame frame in which celestial body coordinates are defined
  156.          * @param frameName name to use (if null a default name will be built)
  157.          */
  158.         InertiallyOriented(final Frame definingFrame, final String frameName) {
  159.             super(definingFrame, new TransformProvider() {

  160.                 /** Serializable UID. */
  161.                 private static final long serialVersionUID = -8610328386110652400L;

  162.                 /** {@inheritDoc} */
  163.                 public Transform getTransform(final AbsoluteDate date) {

  164.                     // compute translation from parent frame to self
  165.                     final PVCoordinates pv = getPVCoordinates(date, definingFrame);
  166.                     final Transform translation = new Transform(date, pv.negate());

  167.                     // compute rotation from ICRF frame to self,
  168.                     // as per the "Report of the IAU/IAG Working Group on Cartographic
  169.                     // Coordinates and Rotational Elements of the Planets and Satellites"
  170.                     // These definitions are common for all recent versions of this report
  171.                     // published every three years, the precise values of pole direction
  172.                     // and W angle coefficients may vary from publication year as models are
  173.                     // adjusted. These coefficients are not in this class, they are in the
  174.                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
  175.                     // methods
  176.                     final Vector3D pole  = iauPole.getPole(date);
  177.                     final Vector3D qNode = iauPole.getNode(date);
  178.                     final Transform rotation =
  179.                             new Transform(date, new Rotation(pole, qNode, Vector3D.PLUS_K, Vector3D.PLUS_I));

  180.                     // update transform from parent to self
  181.                     return new Transform(date, translation, rotation);

  182.                 }

  183.                 @Override
  184.                 public StaticTransform getStaticTransform(final AbsoluteDate date) {
  185.                     // compute translation from parent frame to self
  186.                     final PVCoordinates pv = getPVCoordinates(date, definingFrame);

  187.                     // compute rotation from ICRF frame to self,
  188.                     // as per the "Report of the IAU/IAG Working Group on Cartographic
  189.                     // Coordinates and Rotational Elements of the Planets and Satellites"
  190.                     // These definitions are common for all recent versions of this report
  191.                     // published every three years, the precise values of pole direction
  192.                     // and W angle coefficients may vary from publication year as models are
  193.                     // adjusted. These coefficients are not in this class, they are in the
  194.                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
  195.                     // methods
  196.                     final Vector3D pole  = iauPole.getPole(date);
  197.                     final Vector3D qNode = iauPole.getNode(date);
  198.                     final Rotation rotation =
  199.                             new Rotation(pole, qNode, Vector3D.PLUS_K, Vector3D.PLUS_I);

  200.                     // update transform from parent to self
  201.                     return StaticTransform.of(date, pv.getPosition().negate(), rotation);
  202.                 }

  203.                 /** {@inheritDoc} */
  204.                 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {

  205.                     // compute translation from parent frame to self
  206.                     final FieldPVCoordinates<T> pv = getPVCoordinates(date, definingFrame);
  207.                     final FieldTransform<T> translation = new FieldTransform<>(date, pv.negate());

  208.                     // compute rotation from ICRF frame to self,
  209.                     // as per the "Report of the IAU/IAG Working Group on Cartographic
  210.                     // Coordinates and Rotational Elements of the Planets and Satellites"
  211.                     // These definitions are common for all recent versions of this report
  212.                     // published every three years, the precise values of pole direction
  213.                     // and W angle coefficients may vary from publication year as models are
  214.                     // adjusted. These coefficients are not in this class, they are in the
  215.                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
  216.                     // methods
  217.                     final FieldVector3D<T> pole  = iauPole.getPole(date);
  218.                     FieldVector3D<T> qNode = FieldVector3D.crossProduct(Vector3D.PLUS_K, pole);
  219.                     if (qNode.getNormSq().getReal() < Precision.SAFE_MIN) {
  220.                         qNode = FieldVector3D.getPlusI(date.getField());
  221.                     }
  222.                     final FieldTransform<T> rotation =
  223.                             new FieldTransform<>(date,
  224.                                                  new FieldRotation<>(pole,
  225.                                                                      qNode,
  226.                                                                      FieldVector3D.getPlusK(date.getField()),
  227.                                                                      FieldVector3D.getPlusI(date.getField())));

  228.                     // update transform from parent to self
  229.                     return new FieldTransform<>(date, translation, rotation);

  230.                 }

  231.             }, frameName == null ? name + INERTIAL_FRAME_SUFFIX : frameName, true);
  232.         }

  233.         /** Replace the instance with a data transfer object for serialization.
  234.          * <p>
  235.          * This intermediate class serializes the files supported names, the ephemeris type
  236.          * and the body name.
  237.          * </p>
  238.          * @return data transfer object that will be serialized
  239.          */
  240.         @DefaultDataContext
  241.         private Object writeReplace() {
  242.             return new DTOInertialFrame(supportedNames, generateType, name);
  243.         }

  244.     }

  245.     /** Body oriented body centered frame. */
  246.     private class BodyOriented extends Frame {

  247.         /** Serializable UID. */
  248.         private static final long serialVersionUID = 20170109L;

  249.         /** Suffix for body frame name. */
  250.         private static final String BODY_FRAME_SUFFIX = "/rotating";

  251.         /** Simple constructor.
  252.          * @param frameName name to use (if null a default name will be built)
  253.          */
  254.         BodyOriented(final String frameName) {
  255.             super(inertialFrame, new TransformProvider() {

  256.                 /** Serializable UID. */
  257.                 private static final long serialVersionUID = 20170109L;

  258.                 /** {@inheritDoc} */
  259.                 public Transform getTransform(final AbsoluteDate date) {
  260.                     final double dt = 10.0;
  261.                     final double w0 = iauPole.getPrimeMeridianAngle(date);
  262.                     final double w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
  263.                     return new Transform(date,
  264.                                          new Rotation(Vector3D.PLUS_K, w0, RotationConvention.FRAME_TRANSFORM),
  265.                                          new Vector3D((w1 - w0) / dt, Vector3D.PLUS_K));
  266.                 }

  267.                 /** {@inheritDoc} */
  268.                 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
  269.                     final double dt = 10.0;
  270.                     final T w0 = iauPole.getPrimeMeridianAngle(date);
  271.                     final T w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
  272.                     return new FieldTransform<>(date,
  273.                                                 new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), w0,
  274.                                                                     RotationConvention.FRAME_TRANSFORM),
  275.                                                 new FieldVector3D<>(w1.subtract(w0).divide(dt), Vector3D.PLUS_K));
  276.                 }

  277.             }, frameName == null ? name + BODY_FRAME_SUFFIX : frameName, false);
  278.         }

  279.         /** Replace the instance with a data transfer object for serialization.
  280.          * <p>
  281.          * This intermediate class serializes the files supported names, the ephemeris type
  282.          * and the body name.
  283.          * </p>
  284.          * @return data transfer object that will be serialized
  285.          */
  286.         @DefaultDataContext
  287.         private Object writeReplace() {
  288.             return new DTOBodyFrame(supportedNames, generateType, name);
  289.         }

  290.     }

  291.     /** Internal class used only for serialization. */
  292.     @DefaultDataContext
  293.     private abstract static class DataTransferObject implements Serializable {

  294.         /** Serializable UID. */
  295.         private static final long serialVersionUID = 674742836536072422L;

  296.         /** Regular expression for supported files names. */
  297.         private final String supportedNames;

  298.         /** Ephemeris type to generate. */
  299.         private final EphemerisType generateType;

  300.         /** Name of the body. */
  301.         private final String name;

  302.         /** Simple constructor.
  303.          * @param supportedNames regular expression for supported files names
  304.          * @param generateType ephemeris type to generate
  305.          * @param name name of the body
  306.          */
  307.         DataTransferObject(final String supportedNames, final EphemerisType generateType, final String name) {
  308.             this.supportedNames = supportedNames;
  309.             this.generateType   = generateType;
  310.             this.name           = name;
  311.         }

  312.         /** Get the body associated with the serialized data.
  313.          * @return body associated with the serialized data
  314.          */
  315.         protected JPLCelestialBody getBody() {

  316.             try {
  317.                 // first try to use the factory, in order to avoid building a new instance
  318.                 // each time we deserialize and have the object properly cached
  319.                 final CelestialBody factoryProvided =
  320.                         DataContext.getDefault().getCelestialBodies().getBody(name);
  321.                 if (factoryProvided instanceof JPLCelestialBody) {
  322.                     final JPLCelestialBody jplBody = (JPLCelestialBody) factoryProvided;
  323.                     if (supportedNames.equals(jplBody.supportedNames) && generateType == jplBody.generateType) {
  324.                         // the factory created exactly the object we needed, just return it
  325.                         return jplBody;
  326.                     }
  327.                 }

  328.                 // the factory does not return the object we want
  329.                 // we create a new one from scratch and don't cache it
  330.                 return (JPLCelestialBody) new JPLEphemeridesLoader(supportedNames, generateType).loadCelestialBody(name);

  331.             } catch (OrekitException oe) {
  332.                 throw new OrekitInternalError(oe);
  333.             }

  334.         }

  335.     }

  336.     /** Specialization of the data transfer object for complete celestial body serialization. */
  337.     @DefaultDataContext
  338.     private static class DTOCelestialBody extends DataTransferObject {

  339.         /** Serializable UID. */
  340.         private static final long serialVersionUID = -8287341529741045958L;

  341.         /** Simple constructor.
  342.          * @param supportedNames regular expression for supported files names
  343.          * @param generateType ephemeris type to generate
  344.          * @param name name of the body
  345.          */
  346.         DTOCelestialBody(final String supportedNames, final EphemerisType generateType, final String name) {
  347.             super(supportedNames, generateType, name);
  348.         }

  349.         /** Replace the deserialized data transfer object with a {@link JPLCelestialBody}.
  350.          * @return replacement {@link JPLCelestialBody}
  351.          */
  352.         private Object readResolve() {
  353.             return getBody();
  354.         }

  355.     }

  356.     /** Specialization of the data transfer object for inertially oriented frame serialization. */
  357.     @DefaultDataContext
  358.     private static class DTOInertialFrame extends DataTransferObject {

  359.         /** Serializable UID. */
  360.         private static final long serialVersionUID = 7915071664444154948L;

  361.         /** Simple constructor.
  362.          * @param supportedNames regular expression for supported files names
  363.          * @param generateType ephemeris type to generate
  364.          * @param name name of the body
  365.          */
  366.         DTOInertialFrame(final String supportedNames, final EphemerisType generateType, final String name) {
  367.             super(supportedNames, generateType, name);
  368.         }

  369.         /** Replace the deserialized data transfer object with a {@link Frame}.
  370.          * @return replacement {@link Frame}
  371.          */
  372.         private Object readResolve() {
  373.             return getBody().inertialFrame;
  374.         }

  375.     }

  376.     /** Specialization of the data transfer object for body oriented frame serialization. */
  377.     @DefaultDataContext
  378.     private static class DTOBodyFrame extends DataTransferObject {

  379.         /** Serializable UID. */
  380.         private static final long serialVersionUID = -3194195019557081000L;

  381.         /** Simple constructor.
  382.          * @param supportedNames regular expression for supported files names
  383.          * @param generateType ephemeris type to generate
  384.          * @param name name of the body
  385.          */
  386.         DTOBodyFrame(final String supportedNames, final EphemerisType generateType, final String name) {
  387.             super(supportedNames, generateType, name);
  388.         }

  389.         /** Replace the deserialized data transfer object with a {@link Frame}.
  390.          * @return replacement {@link Frame}
  391.          */
  392.         private Object readResolve() {
  393.             return getBody().bodyFrame;
  394.         }

  395.     }

  396. }