GNSSAttitudeContext.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.analysis.differentiation.DerivativeStructure;
  19. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  22. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.frames.LOFType;
  26. import org.orekit.frames.Transform;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.time.TimeStamped;
  29. import org.orekit.utils.FieldPVCoordinates;
  30. import org.orekit.utils.PVCoordinates;
  31. import org.orekit.utils.TimeStampedAngularCoordinates;
  32. import org.orekit.utils.TimeStampedPVCoordinates;

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

  49.     /** Constant Y axis. */
  50.     private static final PVCoordinates PLUS_Y =
  51.             new PVCoordinates(Vector3D.PLUS_J, Vector3D.ZERO, Vector3D.ZERO);

  52.     /** Constant Z axis. */
  53.     private static final PVCoordinates MINUS_Z =
  54.             new PVCoordinates(Vector3D.MINUS_K, Vector3D.ZERO, Vector3D.ZERO);

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

  57.     /** Derivation order. */
  58.     private static final int ORDER = 2;

  59.     /** Spacecraft position-velocity in inertial frame. */
  60.     private final TimeStampedPVCoordinates svPV;

  61.     /** Spacecraft position-velocity in inertial frame. */
  62.     private final FieldPVCoordinates<DerivativeStructure> svPVDS;

  63.     /** Angle between Sun and orbital plane. */
  64.     private final DerivativeStructure beta;

  65.     /** Cosine of the angle between spacecraft and Sun direction. */
  66.     private final DerivativeStructure svbCos;

  67.     /** Nominal yaw. */
  68.     private final TimeStampedAngularCoordinates nominalYaw;

  69.     /** Nominal yaw. */
  70.     private final FieldRotation<DerivativeStructure> nominalYawDS;

  71.     /** Spacecraft angular velocity. */
  72.     private double muRate;

  73.     /** Limit cosine for the midnight turn. */
  74.     private double cNight;

  75.     /** Limit cosine for the noon turn. */
  76.     private double cNoon;

  77.     /** Relative orbit angle to turn center. */
  78.     private DerivativeStructure delta;

  79.     /** Half span of the turn region, as an angle in orbit plane. */
  80.     private double halfSpan;

  81.     /** Turn start date. */
  82.     private AbsoluteDate turnStart;

  83.     /** Turn end date. */
  84.     private AbsoluteDate turnEnd;

  85.     /** Simple constructor.
  86.      * @param sunPV Sun position-velocity in inertial frame
  87.      * @param svPV spacecraft position-velocity in inertial frame
  88.      * @exception OrekitException if yaw cannot be corrected
  89.      */
  90.     GNSSAttitudeContext(final TimeStampedPVCoordinates sunPV, final TimeStampedPVCoordinates svPV)
  91.         throws OrekitException {

  92.         final FieldPVCoordinates<DerivativeStructure> sunPVDS = sunPV.toDerivativeStructurePV(ORDER);
  93.         this.svPV    = svPV;
  94.         this.svPVDS  = svPV.toDerivativeStructurePV(ORDER);
  95.         this.svbCos  = FieldVector3D.dotProduct(sunPVDS.getPosition(), svPVDS.getPosition()).
  96.                        divide(sunPVDS.getPosition().getNorm().
  97.                               multiply(svPVDS.getPosition().getNorm()));
  98.         this.beta    = FieldVector3D.angle(sunPVDS.getPosition(), svPVDS.getMomentum()).
  99.                        negate().
  100.                        add(0.5 * FastMath.PI);

  101.         // nominal yaw steering
  102.         this.nominalYaw =
  103.                         new TimeStampedAngularCoordinates(svPV.getDate(),
  104.                                                           svPV.normalize(),
  105.                                                           PVCoordinates.crossProduct(sunPV, svPV).normalize(),
  106.                                                           MINUS_Z,
  107.                                                           PLUS_Y,
  108.                                                           1.0e-9);
  109.         this.nominalYawDS = nominalYaw.toDerivativeStructureRotation(ORDER);

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

  111.     }

  112.     /** {@inheritDoc} */
  113.     @Override
  114.     public AbsoluteDate getDate() {
  115.         return svPV.getDate();
  116.     }

  117.     /** Get the cosine of the angle between spacecraft and Sun direction.
  118.      * @return cosine of the angle between spacecraft and Sun direction.
  119.      */
  120.     public double getSVBcos() {
  121.         return svbCos.getValue();
  122.     }

  123.     /** Get the angle between Sun and orbital plane.
  124.      * @return angle between Sun and orbital plane
  125.      * @see #getBetaDS()
  126.      * @see #getSecuredBeta(TurnTimeRange)
  127.      */
  128.     public double getBeta() {
  129.         return beta.getValue();
  130.     }

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

  139.     /** Get a Sun elevation angle that does not change sign within the turn.
  140.      * <p>
  141.      * This method either returns the current beta or replaces it with the
  142.      * value at turn start, so the sign remains constant throughout the
  143.      * turn. As of 9.2, it is only useful for GPS and Glonass.
  144.      * </p>
  145.      * @return secured Sun elevation angle
  146.      * @see #getBeta()
  147.      * @see #getBetaDS()
  148.      */
  149.     public double getSecuredBeta() {
  150.         return FastMath.abs(beta.getReal()) < BETA_SIGN_CHANGE_PROTECTION ?
  151.                beta.taylor(-timeSinceTurnStart(getDate())) :
  152.                getBeta();
  153.     }

  154.     /** Get the nominal yaw.
  155.      * @return nominal yaw
  156.      */
  157.     public TimeStampedAngularCoordinates getNominalYaw() {
  158.         return nominalYaw;
  159.     }

  160.     /** Compute nominal yaw angle.
  161.      * @return nominal yaw angle
  162.      */
  163.     public double yawAngle() {
  164.         final Vector3D xSat = nominalYaw.getRotation().revert().applyTo(Vector3D.PLUS_I);
  165.         return FastMath.copySign(Vector3D.angle(svPV.getVelocity(), xSat), -beta.getReal());
  166.     }

  167.     /** Compute nominal yaw angle.
  168.      * @return nominal yaw angle
  169.      */
  170.     public DerivativeStructure yawAngleDS() {
  171.         final FieldVector3D<DerivativeStructure> xSat = nominalYawDS.revert().applyTo(Vector3D.PLUS_I);
  172.         return FastMath.copySign(FieldVector3D.angle(svPV.getVelocity(), xSat), -beta.getReal());
  173.     }

  174.     /** Set up the midnight/noon turn region.
  175.      * @param cosNight limit cosine for the midnight turn
  176.      * @param cosNoon limit cosine for the noon turn
  177.      * @return true if spacecraft is in the midnight/noon turn region
  178.      */
  179.     public boolean setUpTurnRegion(final double cosNight, final double cosNoon) {
  180.         this.cNight = cosNight;
  181.         this.cNoon  = cosNoon;
  182.         if (svbCos.getValue() < cNight) {
  183.             // in eclipse turn mode
  184.             final DerivativeStructure absDelta = inOrbitPlaneAbsoluteAngle(svbCos.acos().negate().add(FastMath.PI));
  185.             delta = FastMath.copySign(absDelta, -absDelta.getPartialDerivative(1));
  186.             return true;
  187.         } else if (svbCos.getValue() > cNoon) {
  188.             // in noon turn mode
  189.             final DerivativeStructure absDelta = inOrbitPlaneAbsoluteAngle(svbCos.acos());
  190.             delta = FastMath.copySign(absDelta, -absDelta.getPartialDerivative(1));
  191.             return true;
  192.         } else {
  193.             return false;
  194.         }
  195.     }

  196.     /** Get the relative orbit angle to turn center.
  197.      * @return relative orbit angle to turn center
  198.      */
  199.     public double getDelta() {
  200.         return delta.getValue();
  201.     }

  202.     /** Get the relative orbit angle to turn center.
  203.      * @return relative orbit angle to turn center
  204.      */
  205.     public DerivativeStructure getDeltaDS() {
  206.         return delta;
  207.     }

  208.     /** Check if spacecraft is in the half orbit closest to Sun.
  209.      * @return true if spacecraft is in the half orbit closest to Sun
  210.      */
  211.     public boolean inSunSide() {
  212.         return svbCos.getValue() > 0;
  213.     }

  214.     /** Get yaw at turn start.
  215.      * @param sunBeta Sun elevation above orbital plane
  216.      * (it <em>may</em> be different from {@link #getBeta()} in
  217.      * some special cases)
  218.      * @return yaw at turn start
  219.      */
  220.     public double getYawStart(final double sunBeta) {
  221.         return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos.getValue()));
  222.     }

  223.     /** Get yaw at turn end.
  224.      * @param sunBeta Sun elevation above orbital plane
  225.      * (it <em>may</em> be different from {@link #getBeta()} in
  226.      * some special cases)
  227.      * @return yaw at turn end
  228.      */
  229.     public double getYawEnd(final double sunBeta) {
  230.         return computePhi(sunBeta, FastMath.copySign(halfSpan, -svbCos.getValue()));
  231.     }

  232.     /** Compute yaw rate.
  233.      * @param sunBeta Sun elevation above orbital plane
  234.      * (it <em>may</em> be different from {@link #getBeta()} in
  235.      * some special cases)
  236.      * @return yaw rate
  237.      */
  238.     public double yawRate(final double sunBeta) {
  239.         return (getYawEnd(sunBeta) - getYawStart(sunBeta)) / getTurnDuration();
  240.     }

  241.     /** Get the spacecraft angular velocity.
  242.      * @return spacecraft angular velocity
  243.      */
  244.     public double getMuRate() {
  245.         return muRate;
  246.     }

  247.     /** Project a spacecraft/Sun angle into orbital plane.
  248.      * <p>
  249.      * This method is intended to find the limits of the noon and midnight
  250.      * turns in orbital plane. The return angle is signed, depending on the
  251.      * spacecraft being before or after turn middle point.
  252.      * </p>
  253.      * @param angle spacecraft/Sun angle (or spacecraft/opposite-of-Sun)
  254.      * @return angle projected into orbital plane, always positive
  255.      */
  256.     private DerivativeStructure inOrbitPlaneAbsoluteAngle(final DerivativeStructure angle) {
  257.         return FastMath.acos(FastMath.cos(angle).divide(FastMath.cos(beta)));
  258.     }

  259.     /** Project a spacecraft/Sun angle into orbital plane.
  260.      * <p>
  261.      * This method is intended to find the limits of the noon and midnight
  262.      * turns in orbital plane. The return angle is always positive. The
  263.      * correct sign to apply depends on the spacecraft being before or
  264.      * after turn middle point.
  265.      * </p>
  266.      * @param angle spacecraft/Sun angle (or spacecraft/opposite-of-Sun)
  267.      * @return angle projected into orbital plane, always positive
  268.      */
  269.     public double inOrbitPlaneAbsoluteAngle(final double angle) {
  270.         return FastMath.acos(FastMath.cos(angle) / FastMath.cos(beta.getReal()));
  271.     }

  272.     /** Compute yaw.
  273.      * @param sunBeta Sun elevation above orbital plane
  274.      * (it <em>may</em> be different from {@link #getBeta()} in
  275.      * some special cases)
  276.      * @param inOrbitPlaneAngle in orbit angle between spacecraft
  277.      * and Sun (or opposite of Sun) projection
  278.      * @return yaw angle
  279.      */
  280.     public double computePhi(final double sunBeta, final double inOrbitPlaneAngle) {
  281.         return FastMath.atan2(-FastMath.tan(sunBeta), FastMath.sin(inOrbitPlaneAngle));
  282.     }

  283.     /** Set turn half span and compute corresponding turn time range.
  284.      * @param halfSpan half span of the turn region, as an angle in orbit plane
  285.      */
  286.     public void setHalfSpan(final double halfSpan) {
  287.         this.halfSpan  = halfSpan;
  288.         this.turnStart = svPV.getDate().shiftedBy((delta.getValue() - halfSpan) / muRate);
  289.         this.turnEnd   = svPV.getDate().shiftedBy((delta.getValue() + halfSpan) / muRate);
  290.     }

  291.     /** Check if a date is within range.
  292.      * @param date date to check
  293.      * @param endMargin margin in seconds after turn end
  294.      * @return true if date is within range extended by end margin
  295.      */
  296.     public boolean inTurnTimeRange(final AbsoluteDate date, final double endMargin) {
  297.         return date.durationFrom(turnStart) > 0 &&
  298.                date.durationFrom(turnEnd)   < endMargin;
  299.     }

  300.     /** Get turn duration.
  301.      * @return turn duration
  302.      */
  303.     public double getTurnDuration() {
  304.         return 2 * halfSpan / muRate;
  305.     }

  306.     /** Get elapsed time since turn start.
  307.      * @param date date to check
  308.      * @return elapsed time from turn start to specified date
  309.      */
  310.     public double timeSinceTurnStart(final AbsoluteDate date) {
  311.         return date.durationFrom(turnStart);
  312.     }

  313.     /** Generate an attitude with turn-corrected yaw.
  314.      * @param yaw yaw value to apply
  315.      * @param yawDot yaw first time derivative
  316.      * @return attitude with specified yaw
  317.      */
  318.     public TimeStampedAngularCoordinates turnCorrectedAttitude(final double yaw, final double yawDot) {
  319.         return turnCorrectedAttitude(beta.getFactory().build(yaw, yawDot, 0.0));
  320.     }

  321.     /** Generate an attitude with turn-corrected yaw.
  322.      * @param yaw yaw value to apply
  323.      * @return attitude with specified yaw
  324.      */
  325.     public TimeStampedAngularCoordinates turnCorrectedAttitude(final DerivativeStructure yaw) {

  326.         // compute a linear yaw correction model
  327.         final DerivativeStructure nominalAngle   = yawAngleDS();
  328.         final TimeStampedAngularCoordinates correction =
  329.                         new TimeStampedAngularCoordinates(nominalYaw.getDate(),
  330.                                                           new FieldRotation<>(FieldVector3D.getPlusK(nominalAngle.getField()),
  331.                                                                               yaw.subtract(nominalAngle),
  332.                                                                               RotationConvention.VECTOR_OPERATOR));

  333.         // combine the two parts of the attitude
  334.         return correction.addOffset(getNominalYaw());

  335.     }

  336.     /** Compute Orbit Normal (ON) yaw.
  337.      * @return Orbit Normal yaw, using inertial frame as reference
  338.      */
  339.     public TimeStampedAngularCoordinates orbitNormalYaw() {
  340.         final Transform t = LOFType.VVLH.transformFromInertial(svPV.getDate(), svPV);
  341.         return new TimeStampedAngularCoordinates(svPV.getDate(),
  342.                                                  t.getRotation(),
  343.                                                  t.getRotationRate(),
  344.                                                  t.getRotationAcceleration());
  345.     }

  346. }