GNSSFieldAttitudeContext.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.gnss.attitude;

  18. import org.hipparchus.Field;
  19. import org.hipparchus.RealFieldElement;
  20. import org.hipparchus.analysis.differentiation.FDSFactory;
  21. import org.hipparchus.analysis.differentiation.FieldDerivativeStructure;
  22. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.frames.FieldTransform;
  29. import org.orekit.frames.LOFType;
  30. import org.orekit.time.FieldAbsoluteDate;
  31. import org.orekit.time.FieldTimeStamped;
  32. import org.orekit.utils.FieldPVCoordinates;
  33. import org.orekit.utils.PVCoordinates;
  34. import org.orekit.utils.TimeStampedFieldAngularCoordinates;
  35. import org.orekit.utils.TimeStampedFieldPVCoordinates;

  36. /**
  37.  * Boilerplate computations for GNSS attitude.
  38.  *
  39.  * <p>
  40.  * This class is intended to hold throw-away data pertaining to <em>one</em> call
  41.  * to {@link GNSSAttitudeProvider#getAttitude(org.orekit.utils.FieldPVCoordinatesProvider,
  42.  * org.orekit.time.FieldAbsoluteDate, org.orekit.frames.Frame)) getAttitude}. It allows
  43.  * the various {@link GNSSAttitudeProvider} implementations to be immutable as they
  44.  * do not store any state, and hence to be thread-safe, reentrant and naturally
  45.  * serializable (so for example ephemeris built from them are also serializable).
  46.  * </p>
  47.  *
  48.  * @author Luc Maisonobe
  49.  * @since 9.2
  50.  */
  51. class GNSSFieldAttitudeContext<T extends RealFieldElement<T>> implements FieldTimeStamped<T> {

  52.     /** Constant Y axis. */
  53.     private static final PVCoordinates PLUS_Y =
  54.             new PVCoordinates(Vector3D.PLUS_J, Vector3D.ZERO, Vector3D.ZERO);

  55.     /** Constant Z axis. */
  56.     private static final PVCoordinates MINUS_Z =
  57.             new PVCoordinates(Vector3D.MINUS_K, Vector3D.ZERO, Vector3D.ZERO);

  58.     /** Limit value below which we shoud use replace beta by betaIni. */
  59.     private static final double BETA_SIGN_CHANGE_PROTECTION = FastMath.toRadians(0.07);

  60.     /** Derivation order. */
  61.     private static final int ORDER = 2;

  62.     /** Constant Y axis. */
  63.     private final FieldPVCoordinates<T> plusY;

  64.     /** Constant Z axis. */
  65.     private final FieldPVCoordinates<T> minusZ;

  66.     /** Spacecraft position-velocity in inertial frame. */
  67.     private final TimeStampedFieldPVCoordinates<T> svPV;

  68.     /** Spacecraft position-velocity in inertial frame. */
  69.     private final FieldPVCoordinates<FieldDerivativeStructure<T>> svPVDS;

  70.     /** Angle between Sun and orbital plane. */
  71.     private final FieldDerivativeStructure<T> beta;

  72.     /** Cosine of the angle between spacecraft and Sun direction. */
  73.     private final FieldDerivativeStructure<T> svbCos;

  74.     /** Nominal yaw. */
  75.     private final TimeStampedFieldAngularCoordinates<T> nominalYaw;

  76.     /** Nominal yaw. */
  77.     private final FieldRotation<FieldDerivativeStructure<T>> nominalYawDS;

  78.     /** Spacecraft angular velocity. */
  79.     private T muRate;

  80.     /** Limit cosine for the midnight turn. */
  81.     private double cNight;

  82.     /** Limit cosine for the noon turn. */
  83.     private double cNoon;

  84.     /** Relative orbit angle to turn center. */
  85.     private FieldDerivativeStructure<T> delta;

  86.     /** Half span of the turn region, as an angle in orbit plane. */
  87.     private T halfSpan;

  88.     /** Turn start date. */
  89.     private FieldAbsoluteDate<T> turnStart;

  90.     /** Turn end date. */
  91.     private FieldAbsoluteDate<T> turnEnd;

  92.     /** Simple constructor.
  93.      * @param sunPV Sun position-velocity in inertial frame
  94.      * @param svPV spacecraft position-velocity in inertial frame
  95.      * @exception OrekitException if yaw cannot be corrected
  96.      */
  97.     GNSSFieldAttitudeContext(final TimeStampedFieldPVCoordinates<T> sunPV, final TimeStampedFieldPVCoordinates<T> svPV)
  98.         throws OrekitException {

  99.         final Field<T> field = sunPV.getDate().getField();
  100.         plusY  = new FieldPVCoordinates<>(field, PLUS_Y);
  101.         minusZ = new FieldPVCoordinates<>(field, MINUS_Z);

  102.         final FieldPVCoordinates<FieldDerivativeStructure<T>> sunPVDS = sunPV.toDerivativeStructurePV(ORDER);
  103.         this.svPV    = svPV;
  104.         this.svPVDS  = svPV.toDerivativeStructurePV(ORDER);
  105.         this.svbCos  = FieldVector3D.dotProduct(sunPVDS.getPosition(), svPVDS.getPosition()).
  106.                        divide(sunPVDS.getPosition().getNorm().
  107.                        multiply(svPVDS.getPosition().getNorm()));
  108.         this.beta    = FieldVector3D.angle(sunPVDS.getPosition(), svPVDS.getMomentum()).
  109.                        negate().
  110.                        add(0.5 * FastMath.PI);

  111.         // nominal yaw steering
  112.         this.nominalYaw =
  113.                         new TimeStampedFieldAngularCoordinates<>(svPV.getDate(),
  114.                                                                  svPV.normalize(),
  115.                                                                  sunPV.crossProduct(svPV).normalize(),
  116.                                                                  minusZ,
  117.                                                                  plusY,
  118.                                                                  1.0e-9);
  119.         this.nominalYawDS = nominalYaw.toDerivativeStructureRotation(ORDER);

  120.         this.muRate = svPV.getAngularVelocity().getNorm();

  121.     }

  122.     /** {@inheritDoc} */
  123.     @Override
  124.     public FieldAbsoluteDate<T> getDate() {
  125.         return svPV.getDate();
  126.     }

  127.     /** Get the cosine of the angle between spacecraft and Sun direction.
  128.      * @return cosine of the angle between spacecraft and Sun direction.
  129.      */
  130.     public T getSVBcos() {
  131.         return svbCos.getValue();
  132.     }

  133.     /** Get the angle between Sun and orbital plane.
  134.      * @return angle between Sun and orbital plane
  135.      * @see #getBetaDS()
  136.      * @see #getSecuredBeta(TurnTimeRange)
  137.      */
  138.     public T getBeta() {
  139.         return beta.getValue();
  140.     }

  141.     /** Get the angle between Sun and orbital plane.
  142.      * @return angle between Sun and orbital plane
  143.      * @see #getBeta()
  144.      * @see #getSecuredBeta(TurnTimeRange)
  145.      */
  146.     public FieldDerivativeStructure<T> getBetaDS() {
  147.         return beta;
  148.     }

  149.     /** Get a Sun elevation angle that does not change sign within the turn.
  150.      * <p>
  151.      * This method either returns the current beta or replaces it with the
  152.      * value at turn start, so the sign remains constant throughout the
  153.      * turn. As of 9.2, it is only useful for GPS and Glonass.
  154.      * </p>
  155.      * @return secured Sun elevation angle
  156.      * @see #getBeta()
  157.      * @see #getBetaDS()
  158.      */
  159.     public T getSecuredBeta() {
  160.         return FastMath.abs(beta.getReal()) < BETA_SIGN_CHANGE_PROTECTION ?
  161.                beta.taylor(timeSinceTurnStart(getDate()).negate()) :
  162.                getBeta();
  163.     }

  164.     /** Get the nominal yaw.
  165.      * @return nominal yaw
  166.      */
  167.     public TimeStampedFieldAngularCoordinates<T> getNominalYaw() {
  168.         return nominalYaw;
  169.     }

  170.     /** Compute nominal yaw angle.
  171.      * @return nominal yaw angle
  172.      */
  173.     public T yawAngle() {
  174.         final FieldVector3D<T> xSat = nominalYaw.getRotation().revert().applyTo(Vector3D.PLUS_I);
  175.         return FastMath.copySign(FieldVector3D.angle(svPV.getVelocity(), xSat), -beta.getReal());
  176.     }

  177.     /** Compute nominal yaw angle.
  178.      * @return nominal yaw angle
  179.      */
  180.     public FieldDerivativeStructure<T> yawAngleDS() {
  181.         final FieldVector3D<FieldDerivativeStructure<T>> xSat    = nominalYawDS.revert().applyTo(Vector3D.PLUS_I);
  182.         final FDSFactory<T>                              factory = xSat.getX().getFactory();
  183.         final FieldVector3D<T>                           v       = svPV.getVelocity();
  184.         final FieldVector3D<FieldDerivativeStructure<T>> vDS     = new FieldVector3D<>(factory.constant(v.getX()),
  185.                                                                                        factory.constant(v.getY()),
  186.                                                                                        factory.constant(v.getZ()));
  187.         return FieldVector3D.angle(vDS, xSat).copySign(beta.getValue().negate());
  188.     }

  189.     /** Set up the midnight/noon turn region.
  190.      * @param cosNight limit cosine for the midnight turn
  191.      * @param cosNoon limit cosine for the noon turn
  192.      * @return true if spacecraft is in the midnight/noon turn region
  193.      */
  194.     public boolean setUpTurnRegion(final double cosNight, final double cosNoon) {
  195.         this.cNight = cosNight;
  196.         this.cNoon  = cosNoon;
  197.         if (svbCos.getValue().getReal() < cNight) {
  198.             // in eclipse turn mode
  199.             final FieldDerivativeStructure<T> absDelta = inOrbitPlaneAbsoluteAngle(svbCos.acos().negate().add(FastMath.PI));
  200.             delta = absDelta.copySign(absDelta.getPartialDerivative(1).negate());
  201.             return true;
  202.         } else if (svbCos.getValue().getReal() > cNoon) {
  203.             // in noon turn mode
  204.             final FieldDerivativeStructure<T> absDelta = inOrbitPlaneAbsoluteAngle(svbCos.acos());
  205.             delta = absDelta.copySign(absDelta.getPartialDerivative(1).negate());
  206.             return true;
  207.         } else {
  208.             return false;
  209.         }
  210.     }

  211.     /** Get the relative orbit angle to turn center.
  212.      * @return relative orbit angle to turn center
  213.      */
  214.     public T getDelta() {
  215.         return delta.getValue();
  216.     }

  217.     /** Get the relative orbit angle to turn center.
  218.      * @return relative orbit angle to turn center
  219.      */
  220.     public FieldDerivativeStructure<T> getDeltaDS() {
  221.         return delta;
  222.     }

  223.     /** Check if spacecraft is in the half orbit closest to Sun.
  224.      * @return true if spacecraft is in the half orbit closest to Sun
  225.      */
  226.     public boolean inSunSide() {
  227.         return svbCos.getValue().getReal() > 0;
  228.     }

  229.     /** Get yaw at turn start.
  230.      * @param sunBeta Sun elevation above orbital plane
  231.      * (it <em>may</em> be different from {@link #getBeta()} in
  232.      * some special cases)
  233.      * @return yaw at turn start
  234.      */
  235.     public T getYawStart(final T sunBeta) {
  236.         return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos.getValue()));
  237.     }

  238.     /** Get yaw at turn end.
  239.      * @param sunBeta Sun elevation above orbital plane
  240.      * (it <em>may</em> be different from {@link #getBeta()} in
  241.      * some special cases)
  242.      * @return yaw at turn end
  243.      */
  244.     public T getYawEnd(final T sunBeta) {
  245.         return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos.getValue().negate()));
  246.     }

  247.     /** Compute yaw rate.
  248.      * @param sunBeta Sun elevation above orbital plane
  249.      * (it <em>may</em> be different from {@link #getBeta()} in
  250.      * some special cases)
  251.      * @return yaw rate
  252.      */
  253.     public T yawRate(final T sunBeta) {
  254.         return getYawEnd(sunBeta).subtract(getYawStart(sunBeta)).divide(getTurnDuration());
  255.     }

  256.     /** Get the spacecraft angular velocity.
  257.      * @return spacecraft angular velocity
  258.      */
  259.     public T getMuRate() {
  260.         return muRate;
  261.     }

  262.     /** Project a spacecraft/Sun angle into orbital plane.
  263.      * <p>
  264.      * This method is intended to find the limits of the noon and midnight
  265.      * turns in orbital plane. The return angle is signed, depending on the
  266.      * spacecraft being before or after turn middle point.
  267.      * </p>
  268.      * @param angle spacecraft/Sun angle (or spacecraft/opposite-of-Sun)
  269.      * @return angle projected into orbital plane, always positive
  270.      */
  271.     private FieldDerivativeStructure<T> inOrbitPlaneAbsoluteAngle(final FieldDerivativeStructure<T> angle) {
  272.         return FastMath.acos(FastMath.cos(angle).divide(FastMath.cos(beta)));
  273.     }

  274.     /** Project a spacecraft/Sun angle into orbital plane.
  275.      * <p>
  276.      * This method is intended to find the limits of the noon and midnight
  277.      * turns in orbital plane. The return angle is always positive. The
  278.      * correct sign to apply depends on the spacecraft being before or
  279.      * after turn middle point.
  280.      * </p>
  281.      * @param angle spacecraft/Sun angle (or spacecraft/opposite-of-Sun)
  282.      * @return angle projected into orbital plane, always positive
  283.      */
  284.     public T inOrbitPlaneAbsoluteAngle(final T angle) {
  285.         return FastMath.acos(FastMath.cos(angle).divide(FastMath.cos(beta.getReal())));
  286.     }

  287.     /** Compute yaw.
  288.      * @param sunBeta Sun elevation above orbital plane
  289.      * (it <em>may</em> be different from {@link #getBeta()} in
  290.      * some special cases)
  291.      * @param inOrbitPlaneAngle in orbit angle between spacecraft
  292.      * and Sun (or opposite of Sun) projection
  293.      * @return yaw angle
  294.      */
  295.     public T computePhi(final T sunBeta, final T inOrbitPlaneAngle) {
  296.         return FastMath.atan2(FastMath.tan(sunBeta).negate(), FastMath.sin(inOrbitPlaneAngle));
  297.     }

  298.     /** Set turn half span and compute corresponding turn time range.
  299.      * @param halfSpan half span of the turn region, as an angle in orbit plane
  300.      */
  301.     public void setHalfSpan(final T halfSpan) {
  302.         this.halfSpan  = halfSpan;
  303.         this.turnStart = svPV.getDate().shiftedBy(delta.getValue().subtract(halfSpan).divide(muRate));
  304.         this.turnEnd   = svPV.getDate().shiftedBy(delta.getValue().add(halfSpan).divide(muRate));
  305.     }

  306.     /** Check if a date is within range.
  307.      * @param date date to check
  308.      * @param endMargin margin in seconds after turn end
  309.      * @return true if date is within range extended by end margin
  310.      */
  311.     public boolean inTurnTimeRange(final FieldAbsoluteDate<T> date, final double endMargin) {
  312.         return date.durationFrom(turnStart).getReal() > 0 &&
  313.                date.durationFrom(turnEnd).getReal()   < endMargin;
  314.     }

  315.     /** Get turn duration.
  316.      * @return turn duration
  317.      */
  318.     public T getTurnDuration() {
  319.         return halfSpan.multiply(2).divide(muRate);
  320.     }

  321.     /** Get elapsed time since turn start.
  322.      * @param date date to check
  323.      * @return elapsed time from turn start to specified date
  324.      */
  325.     public T timeSinceTurnStart(final FieldAbsoluteDate<T> date) {
  326.         return date.durationFrom(turnStart);
  327.     }

  328.     /** Generate an attitude with turn-corrected yaw.
  329.      * @param yaw yaw value to apply
  330.      * @param yawDot yaw first time derivative
  331.      * @return attitude with specified yaw
  332.      */
  333.     public TimeStampedFieldAngularCoordinates<T> turnCorrectedAttitude(final T yaw, final T yawDot) {
  334.         return turnCorrectedAttitude(beta.getFactory().build(yaw, yawDot, yaw.getField().getZero()));
  335.     }

  336.     /** Generate an attitude with turn-corrected yaw.
  337.      * @param yaw yaw value to apply
  338.      * @return attitude with specified yaw
  339.      */
  340.     public TimeStampedFieldAngularCoordinates<T> turnCorrectedAttitude(final FieldDerivativeStructure<T> yaw) {

  341.         // compute a linear yaw correction model
  342.         final FieldDerivativeStructure<T> nominalAngle   = yawAngleDS();
  343.         final TimeStampedFieldAngularCoordinates<T> correction =
  344.                         new TimeStampedFieldAngularCoordinates<>(nominalYaw.getDate(),
  345.                                                                  new FieldRotation<>(FieldVector3D.getPlusK(nominalAngle.getField()),
  346.                                                                                      yaw.subtract(nominalAngle),
  347.                                                                                      RotationConvention.VECTOR_OPERATOR));

  348.         // combine the two parts of the attitude
  349.         return correction.addOffset(getNominalYaw());

  350.     }

  351.     /** Compute Orbit Normal (ON) yaw.
  352.      * @return Orbit Normal yaw, using inertial frame as reference
  353.      */
  354.     public TimeStampedFieldAngularCoordinates<T> orbitNormalYaw() {
  355.         final FieldTransform<T> t = LOFType.VVLH.transformFromInertial(svPV.getDate(), svPV);
  356.         return new TimeStampedFieldAngularCoordinates<>(svPV.getDate(),
  357.                                                         t.getRotation(),
  358.                                                         t.getRotationRate(),
  359.                                                         t.getRotationAcceleration());
  360.     }

  361. }