JPLCelestialBody.java

  1. /* Copyright 2002-2023 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.Field;
  21. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Rotation;
  24. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.hipparchus.util.Precision;
  27. import org.orekit.annotation.DefaultDataContext;
  28. import org.orekit.bodies.JPLEphemeridesLoader.EphemerisType;
  29. import org.orekit.data.DataContext;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.errors.OrekitInternalError;
  32. import org.orekit.frames.FieldStaticTransform;
  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. /** Implementation of the {@link CelestialBody} interface using JPL or INPOP ephemerides.
  45.  * @author Luc Maisonobe
  46.  */
  47. class JPLCelestialBody implements CelestialBody {

  48.     /** Serializable UID. */
  49.     private static final long serialVersionUID = 3809787672779740923L;

  50.     /** Name of the body. */
  51.     private final String name;

  52.     /** Regular expression for supported files names. */
  53.     private final String supportedNames;

  54.     /** Ephemeris type to generate. */
  55.     private final JPLEphemeridesLoader.EphemerisType generateType;

  56.     /** Raw position-velocity provider. */
  57.     private final transient JPLEphemeridesLoader.RawPVProvider rawPVProvider;

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

  60.     /** Scaling factor for position-velocity. */
  61.     private final double scale;

  62.     /** IAU pole. */
  63.     private final IAUPole iauPole;

  64.     /** Inertially oriented, body-centered frame. */
  65.     private final Frame inertialFrame;

  66.     /** Body oriented, body-centered frame. */
  67.     private final Frame bodyFrame;

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

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

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

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

  104.         // convert to requested frame
  105.         return transform.transformPVCoordinates(scaledPV);

  106.     }

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

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

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

  120.         // convert to requested frame
  121.         return transform.transformPVCoordinates(scaledPV);

  122.     }

  123.     /** {@inheritDoc} */
  124.     @Override
  125.     public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {

  126.         // apply the scale factor to raw position
  127.         final Vector3D rawPosition    = rawPVProvider.getRawPosition(date);
  128.         final Vector3D scaledPosition = rawPosition.scalarMultiply(scale);

  129.         // the raw position is relative to the parent of the body centered inertially oriented frame
  130.         final StaticTransform transform = getInertiallyOrientedFrame().getParent().getStaticTransformTo(frame, date);

  131.         // convert to requested frame
  132.         return transform.transformPosition(scaledPosition);
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public <T extends CalculusFieldElement<T>> FieldVector3D<T> getPosition(final FieldAbsoluteDate<T> date, final Frame frame) {

  137.         // apply the scale factor to raw position
  138.         final FieldVector3D<T> rawPosition     = rawPVProvider.getRawPosition(date);
  139.         final FieldVector3D<T> scaledPosition  = rawPosition.scalarMultiply(scale);

  140.         // the raw position is relative to the parent of the body centered inertially oriented frame
  141.         final FieldStaticTransform<T> transform = getInertiallyOrientedFrame().getParent().getStaticTransformTo(frame, date);

  142.         // convert to requested frame
  143.         return transform.transformPosition(scaledPosition);
  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.     /** {@inheritDoc} */
  157.     public String getName() {
  158.         return name;
  159.     }

  160.     /** {@inheritDoc} */
  161.     public double getGM() {
  162.         return gm;
  163.     }

  164.     /** {@inheritDoc} */
  165.     public Frame getInertiallyOrientedFrame() {
  166.         return inertialFrame;
  167.     }

  168.     /** {@inheritDoc} */
  169.     public Frame getBodyOrientedFrame() {
  170.         return bodyFrame;
  171.     }

  172.     /** Inertially oriented body centered frame. */
  173.     private class InertiallyOriented extends Frame {

  174.         /** Serializable UID. */
  175.         private static final long serialVersionUID = -8849993808761896559L;

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

  178.         /** Simple constructor.
  179.          * @param definingFrame frame in which celestial body coordinates are defined
  180.          * @param frameName name to use (if null a default name will be built)
  181.          */
  182.         InertiallyOriented(final Frame definingFrame, final String frameName) {
  183.             super(definingFrame, new TransformProvider() {

  184.                 /** Serializable UID. */
  185.                 private static final long serialVersionUID = -8610328386110652400L;

  186.                 /** {@inheritDoc} */
  187.                 public Transform getTransform(final AbsoluteDate date) {

  188.                     // compute translation from parent frame to self
  189.                     final PVCoordinates pv = getPVCoordinates(date, definingFrame);
  190.                     final Transform translation = new Transform(date, pv.negate());

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

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

  206.                 }

  207.                 @Override
  208.                 public StaticTransform getStaticTransform(final AbsoluteDate date) {
  209.                     // compute translation from parent frame to self
  210.                     final PVCoordinates pv = getPVCoordinates(date, definingFrame);

  211.                     // compute rotation from ICRF frame to self,
  212.                     // as per the "Report of the IAU/IAG Working Group on Cartographic
  213.                     // Coordinates and Rotational Elements of the Planets and Satellites"
  214.                     // These definitions are common for all recent versions of this report
  215.                     // published every three years, the precise values of pole direction
  216.                     // and W angle coefficients may vary from publication year as models are
  217.                     // adjusted. These coefficients are not in this class, they are in the
  218.                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
  219.                     // methods
  220.                     final Vector3D pole  = iauPole.getPole(date);
  221.                     final Vector3D qNode = iauPole.getNode(date);
  222.                     final Rotation rotation =
  223.                                     new Rotation(pole, qNode, Vector3D.PLUS_K, Vector3D.PLUS_I);

  224.                     // update transform from parent to self
  225.                     return StaticTransform.of(date, pv.getPosition().negate(), rotation);
  226.                 }

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

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

  232.                     // compute rotation from ICRF frame to self,
  233.                     // as per the "Report of the IAU/IAG Working Group on Cartographic
  234.                     // Coordinates and Rotational Elements of the Planets and Satellites"
  235.                     // These definitions are common for all recent versions of this report
  236.                     // published every three years, the precise values of pole direction
  237.                     // and W angle coefficients may vary from publication year as models are
  238.                     // adjusted. These coefficients are not in this class, they are in the
  239.                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
  240.                     // methods
  241.                     final FieldVector3D<T> pole  = iauPole.getPole(date);
  242.                     FieldVector3D<T> qNode = FieldVector3D.crossProduct(Vector3D.PLUS_K, pole);
  243.                     if (qNode.getNormSq().getReal() < Precision.SAFE_MIN) {
  244.                         qNode = FieldVector3D.getPlusI(date.getField());
  245.                     }
  246.                     final FieldTransform<T> rotation =
  247.                                     new FieldTransform<>(date,
  248.                                                     new FieldRotation<>(pole,
  249.                                                                     qNode,
  250.                                                                     FieldVector3D.getPlusK(date.getField()),
  251.                                                                     FieldVector3D.getPlusI(date.getField())));

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

  254.                 }

  255.                 @Override
  256.                 public <T extends CalculusFieldElement<T>> FieldStaticTransform<T> getStaticTransform(final FieldAbsoluteDate<T> date) {
  257.                     // field
  258.                     final Field<T> field = date.getField();
  259.                     // compute translation from parent frame to self
  260.                     final FieldPVCoordinates<T> pv = getPVCoordinates(date, definingFrame);

  261.                     // compute rotation from ICRF frame to self,
  262.                     // as per the "Report of the IAU/IAG Working Group on Cartographic
  263.                     // Coordinates and Rotational Elements of the Planets and Satellites"
  264.                     // These definitions are common for all recent versions of this report
  265.                     // published every three years, the precise values of pole direction
  266.                     // and W angle coefficients may vary from publication year as models are
  267.                     // adjusted. These coefficients are not in this class, they are in the
  268.                     // specialized classes that do implement the getPole and getPrimeMeridianAngle
  269.                     // methods
  270.                     final FieldVector3D<T> pole  = iauPole.getPole(date);
  271.                     final FieldVector3D<T> qNode = iauPole.getNode(date);
  272.                     final FieldRotation<T> rotation =
  273.                                     new FieldRotation<>(pole, qNode, FieldVector3D.getPlusK(field), FieldVector3D.getPlusI(field));

  274.                     // update transform from parent to self
  275.                     return FieldStaticTransform.of(date, pv.getPosition().negate(), rotation);
  276.                 }

  277.             }, frameName == null ? name + INERTIAL_FRAME_SUFFIX : frameName, true);
  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 DTOInertialFrame(supportedNames, generateType, name);
  289.         }

  290.     }

  291.     /** Body oriented body centered frame. */
  292.     private class BodyOriented extends Frame {

  293.         /** Serializable UID. */
  294.         private static final long serialVersionUID = 20170109L;

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

  297.         /** Simple constructor.
  298.          * @param frameName name to use (if null a default name will be built)
  299.          */
  300.         BodyOriented(final String frameName) {
  301.             super(inertialFrame, new TransformProvider() {

  302.                 /** Serializable UID. */
  303.                 private static final long serialVersionUID = 20170109L;

  304.                 /** {@inheritDoc} */
  305.                 public Transform getTransform(final AbsoluteDate date) {
  306.                     final double dt = 10.0;
  307.                     final double w0 = iauPole.getPrimeMeridianAngle(date);
  308.                     final double w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
  309.                     return new Transform(date,
  310.                                          new Rotation(Vector3D.PLUS_K, w0, RotationConvention.FRAME_TRANSFORM),
  311.                                          new Vector3D((w1 - w0) / dt, Vector3D.PLUS_K));
  312.                 }

  313.                 /** {@inheritDoc} */
  314.                 public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
  315.                     final double dt = 10.0;
  316.                     final T w0 = iauPole.getPrimeMeridianAngle(date);
  317.                     final T w1 = iauPole.getPrimeMeridianAngle(date.shiftedBy(dt));
  318.                     return new FieldTransform<>(date,
  319.                                     new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), w0,
  320.                                                     RotationConvention.FRAME_TRANSFORM),
  321.                                     new FieldVector3D<>(w1.subtract(w0).divide(dt), Vector3D.PLUS_K));
  322.                 }

  323.             }, frameName == null ? name + BODY_FRAME_SUFFIX : frameName, false);
  324.         }

  325.         /** Replace the instance with a data transfer object for serialization.
  326.          * <p>
  327.          * This intermediate class serializes the files supported names, the ephemeris type
  328.          * and the body name.
  329.          * </p>
  330.          * @return data transfer object that will be serialized
  331.          */
  332.         @DefaultDataContext
  333.         private Object writeReplace() {
  334.             return new DTOBodyFrame(supportedNames, generateType, name);
  335.         }

  336.     }

  337.     /** Internal class used only for serialization. */
  338.     @DefaultDataContext
  339.     private abstract static class DataTransferObject implements Serializable {

  340.         /** Serializable UID. */
  341.         private static final long serialVersionUID = 674742836536072422L;

  342.         /** Regular expression for supported files names. */
  343.         private final String supportedNames;

  344.         /** Ephemeris type to generate. */
  345.         private final EphemerisType generateType;

  346.         /** Name of the body. */
  347.         private final String name;

  348.         /** Simple constructor.
  349.          * @param supportedNames regular expression for supported files names
  350.          * @param generateType ephemeris type to generate
  351.          * @param name name of the body
  352.          */
  353.         DataTransferObject(final String supportedNames, final EphemerisType generateType, final String name) {
  354.             this.supportedNames = supportedNames;
  355.             this.generateType   = generateType;
  356.             this.name           = name;
  357.         }

  358.         /** Get the body associated with the serialized data.
  359.          * @return body associated with the serialized data
  360.          */
  361.         protected JPLCelestialBody getBody() {

  362.             try {
  363.                 // first try to use the factory, in order to avoid building a new instance
  364.                 // each time we deserialize and have the object properly cached
  365.                 final CelestialBody factoryProvided =
  366.                                 DataContext.getDefault().getCelestialBodies().getBody(name);
  367.                 if (factoryProvided instanceof JPLCelestialBody) {
  368.                     final JPLCelestialBody jplBody = (JPLCelestialBody) factoryProvided;
  369.                     if (supportedNames.equals(jplBody.supportedNames) && generateType == jplBody.generateType) {
  370.                         // the factory created exactly the object we needed, just return it
  371.                         return jplBody;
  372.                     }
  373.                 }

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

  377.             } catch (OrekitException oe) {
  378.                 throw new OrekitInternalError(oe);
  379.             }

  380.         }

  381.     }

  382.     /** Specialization of the data transfer object for complete celestial body serialization. */
  383.     @DefaultDataContext
  384.     private static class DTOCelestialBody extends DataTransferObject {

  385.         /** Serializable UID. */
  386.         private static final long serialVersionUID = -8287341529741045958L;

  387.         /** Simple constructor.
  388.          * @param supportedNames regular expression for supported files names
  389.          * @param generateType ephemeris type to generate
  390.          * @param name name of the body
  391.          */
  392.         DTOCelestialBody(final String supportedNames, final EphemerisType generateType, final String name) {
  393.             super(supportedNames, generateType, name);
  394.         }

  395.         /** Replace the deserialized data transfer object with a {@link JPLCelestialBody}.
  396.          * @return replacement {@link JPLCelestialBody}
  397.          */
  398.         private Object readResolve() {
  399.             return getBody();
  400.         }

  401.     }

  402.     /** Specialization of the data transfer object for inertially oriented frame serialization. */
  403.     @DefaultDataContext
  404.     private static class DTOInertialFrame extends DataTransferObject {

  405.         /** Serializable UID. */
  406.         private static final long serialVersionUID = 7915071664444154948L;

  407.         /** Simple constructor.
  408.          * @param supportedNames regular expression for supported files names
  409.          * @param generateType ephemeris type to generate
  410.          * @param name name of the body
  411.          */
  412.         DTOInertialFrame(final String supportedNames, final EphemerisType generateType, final String name) {
  413.             super(supportedNames, generateType, name);
  414.         }

  415.         /** Replace the deserialized data transfer object with a {@link Frame}.
  416.          * @return replacement {@link Frame}
  417.          */
  418.         private Object readResolve() {
  419.             return getBody().inertialFrame;
  420.         }

  421.     }

  422.     /** Specialization of the data transfer object for body oriented frame serialization. */
  423.     @DefaultDataContext
  424.     private static class DTOBodyFrame extends DataTransferObject {

  425.         /** Serializable UID. */
  426.         private static final long serialVersionUID = -3194195019557081000L;

  427.         /** Simple constructor.
  428.          * @param supportedNames regular expression for supported files names
  429.          * @param generateType ephemeris type to generate
  430.          * @param name name of the body
  431.          */
  432.         DTOBodyFrame(final String supportedNames, final EphemerisType generateType, final String name) {
  433.             super(supportedNames, generateType, name);
  434.         }

  435.         /** Replace the deserialized data transfer object with a {@link Frame}.
  436.          * @return replacement {@link Frame}
  437.          */
  438.         private Object readResolve() {
  439.             return getBody().bodyFrame;
  440.         }

  441.     }

  442. }