GNSSAttitudeContext.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.gnss.attitude;

  18. import org.hipparchus.analysis.UnivariateFunction;
  19. import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
  20. import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
  21. import org.hipparchus.analysis.solvers.UnivariateSolverUtils;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.FieldSinCos;
  26. import org.hipparchus.util.SinCos;
  27. import org.orekit.frames.Frame;
  28. import org.orekit.frames.LOFType;
  29. import org.orekit.frames.Transform;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.FieldAbsoluteDate;
  32. import org.orekit.time.TimeStamped;
  33. import org.orekit.utils.ExtendedPVCoordinatesProvider;
  34. import org.orekit.utils.FieldPVCoordinates;
  35. import org.orekit.utils.PVCoordinates;
  36. import org.orekit.utils.PVCoordinatesProvider;
  37. import org.orekit.utils.TimeStampedAngularCoordinates;
  38. import org.orekit.utils.TimeStampedPVCoordinates;

  39. /**
  40.  * Boilerplate computations for GNSS attitude.
  41.  *
  42.  * <p>
  43.  * This class is intended to hold throw-away data pertaining to <em>one</em> call
  44.  * to {@link GNSSAttitudeProvider#getAttitude(org.orekit.utils.PVCoordinatesProvider,
  45.  * org.orekit.time.AbsoluteDate, org.orekit.frames.Frame) getAttitude}.
  46.  * </p>
  47.  *
  48.  * @author Luc Maisonobe
  49.  * @since 9.2
  50.  */
  51. class GNSSAttitudeContext implements TimeStamped {

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

  55.     /** Constant Z axis. */
  56.     private static final PVCoordinates MINUS_Z_PV =
  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.     /** Current date. */
  61.     private final AbsoluteDate date;

  62.     /** Provider for Sun position. */
  63.     private final ExtendedPVCoordinatesProvider sun;

  64.     /** Provider for spacecraft position. */
  65.     private final PVCoordinatesProvider pvProv;

  66.     /** Spacecraft position at central date.
  67.      * @since 12.0
  68.      */
  69.     private final TimeStampedPVCoordinates svPV;

  70.     /** Sun position at central date.
  71.      * @since 12.0
  72.      */
  73.     private final TimeStampedPVCoordinates sunPV;

  74.     /** Inertial frame where velocity are computed. */
  75.     private final Frame inertialFrame;

  76.     /** Cosine of the angle between spacecraft and Sun direction. */
  77.     private final double svbCos;

  78.     /** Morning/Evening half orbit indicator. */
  79.     private final boolean morning;

  80.     /** Relative orbit angle to turn center.
  81.      * @since 12.0
  82.      */
  83.     private UnivariateDerivative2 delta;

  84.     /** Sun elevation at center.
  85.      * @since 12.0
  86.      */
  87.     private UnivariateDerivative2 beta;

  88.     /** Spacecraft angular velocity. */
  89.     private double muRate;

  90.     /** Limit cosine for the midnight turn. */
  91.     private double cNight;

  92.     /** Limit cosine for the noon turn. */
  93.     private double cNoon;

  94.     /** Turn time data. */
  95.     private TurnSpan turnSpan;

  96.     /** Simple constructor.
  97.      * @param date current date
  98.      * @param sun provider for Sun position
  99.      * @param pvProv provider for spacecraft position
  100.      * @param inertialFrame inertial frame where velocity are computed
  101.      * @param turnSpan turn time data, if a turn has already been identified in the date neighborhood,
  102.      * null otherwise
  103.      */
  104.     GNSSAttitudeContext(final AbsoluteDate date,
  105.                         final ExtendedPVCoordinatesProvider sun, final PVCoordinatesProvider pvProv,
  106.                         final Frame inertialFrame, final TurnSpan turnSpan) {

  107.         this.date          = date;
  108.         this.sun           = sun;
  109.         this.pvProv        = pvProv;
  110.         this.inertialFrame = inertialFrame;
  111.         this.sunPV         = sun.getPVCoordinates(date, inertialFrame);
  112.         this.svPV          = pvProv.getPVCoordinates(date, inertialFrame);
  113.         this.morning       = Vector3D.dotProduct(svPV.getVelocity(), sunPV.getPosition()) >= 0.0;
  114.         this.muRate        = svPV.getAngularVelocity().getNorm();
  115.         this.turnSpan      = turnSpan;

  116.         final FieldPVCoordinates<UnivariateDerivative2> sunPVD2 = sunPV.toUnivariateDerivative2PV();
  117.         final FieldPVCoordinates<UnivariateDerivative2> svPVD2  = svPV.toUnivariateDerivative2PV();
  118.         final UnivariateDerivative2 svbCosD2  = FieldVector3D.dotProduct(sunPVD2.getPosition(), svPVD2.getPosition()).
  119.                                                 divide(sunPVD2.getPosition().getNorm().multiply(svPVD2.getPosition().getNorm()));
  120.         svbCos = svbCosD2.getValue();

  121.         beta  = FieldVector3D.angle(sunPVD2.getPosition(), svPVD2.getMomentum()).negate().add(0.5 * FastMath.PI);

  122.         final UnivariateDerivative2 absDelta;
  123.         if (svbCos <= 0) {
  124.             // night side
  125.             absDelta = FastMath.acos(svbCosD2.negate().divide(FastMath.cos(beta)));
  126.         } else {
  127.             // Sun side
  128.             absDelta = FastMath.acos(svbCosD2.divide(FastMath.cos(beta)));
  129.         }
  130.         delta = FastMath.copySign(absDelta, -absDelta.getPartialDerivative(1));

  131.     }

  132.     /** Compute nominal yaw steering.
  133.      * @param d computation date
  134.      * @return nominal yaw steering
  135.      */
  136.     public TimeStampedAngularCoordinates nominalYaw(final AbsoluteDate d) {
  137.         final PVCoordinates pv = pvProv.getPVCoordinates(d, inertialFrame);
  138.         return new TimeStampedAngularCoordinates(d,
  139.                                                  pv.normalize(),
  140.                                                  PVCoordinates.crossProduct(sun.getPVCoordinates(d, inertialFrame), pv).normalize(),
  141.                                                  MINUS_Z_PV,
  142.                                                  PLUS_Y_PV,
  143.                                                  1.0e-9);
  144.     }

  145.     /** Compute Sun elevation.
  146.      * @param d computation date
  147.      * @return Sun elevation
  148.      */
  149.     public double beta(final AbsoluteDate d) {
  150.         final TimeStampedPVCoordinates pv = pvProv.getPVCoordinates(d, inertialFrame);
  151.         return 0.5 * FastMath.PI - Vector3D.angle(sun.getPosition(d, inertialFrame), pv.getMomentum());
  152.     }

  153.     /** Compute Sun elevation.
  154.      * @return Sun elevation
  155.      */
  156.     public UnivariateDerivative2 betaD2() {
  157.         return beta;
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public AbsoluteDate getDate() {
  162.         return date;
  163.     }

  164.     /** Get the turn span.
  165.      * @return turn span, may be null if context is outside of turn
  166.      */
  167.     public TurnSpan getTurnSpan() {
  168.         return turnSpan;
  169.     }

  170.     /** Get the cosine of the angle between spacecraft and Sun direction.
  171.      * @return cosine of the angle between spacecraft and Sun direction.
  172.      */
  173.     public double getSVBcos() {
  174.         return svbCos;
  175.     }

  176.     /** Get a Sun elevation angle that does not change sign within the turn.
  177.      * <p>
  178.      * This method either returns the current beta or replaces it with the
  179.      * value at turn start, so the sign remains constant throughout the
  180.      * turn. As of 9.2, it is used for GPS, Glonass and Galileo.
  181.      * </p>
  182.      * @return secured Sun elevation angle
  183.      * @see #beta(AbsoluteDate)
  184.      * @see #betaDS(FieldAbsoluteDate)
  185.      */
  186.     public double getSecuredBeta() {
  187.         return FastMath.abs(beta.getValue()) < BETA_SIGN_CHANGE_PROTECTION ?
  188.                beta(turnSpan.getTurnStartDate()) :
  189.                beta.getValue();
  190.     }

  191.     /** Check if a linear yaw model is still active or if we already reached target yaw.
  192.      * @param linearPhi value of the linear yaw model
  193.      * @param phiDot slope of the linear yaw model
  194.      * @return true if linear model is still active
  195.      */
  196.     public boolean linearModelStillActive(final double linearPhi, final double phiDot) {
  197.         final double dt0 = turnSpan.getTurnEndDate().durationFrom(date);
  198.         final UnivariateFunction yawReached = dt -> {
  199.             final AbsoluteDate  t       = date.shiftedBy(dt);
  200.             final Vector3D      pSun    = sun.getPosition(t, inertialFrame);
  201.             final PVCoordinates pv      = pvProv.getPVCoordinates(t, inertialFrame);
  202.             final Vector3D      pSat    = pv.getPosition();
  203.             final Vector3D      targetX = Vector3D.crossProduct(pSat, Vector3D.crossProduct(pSun, pSat)).normalize();

  204.             final double        phi         = linearPhi + dt * phiDot;
  205.             final SinCos        sc          = FastMath.sinCos(phi);
  206.             final Vector3D      pU          = pv.getPosition().normalize();
  207.             final Vector3D      mU          = pv.getMomentum().normalize();
  208.             final Vector3D      omega       = new Vector3D(-phiDot, pU);
  209.             final Vector3D      currentX    = new Vector3D(-sc.sin(), mU, -sc.cos(), Vector3D.crossProduct(pU, mU));
  210.             final Vector3D      currentXDot = Vector3D.crossProduct(omega, currentX);

  211.             return Vector3D.dotProduct(targetX, currentXDot);
  212.         };
  213.         final double fullTurn = 2 * FastMath.PI / FastMath.abs(phiDot);
  214.         final double dtMin    = FastMath.min(turnSpan.getTurnStartDate().durationFrom(date), dt0 - 60.0);
  215.         final double dtMax    = FastMath.max(dtMin + fullTurn, dt0 + 60.0);
  216.         double[] bracket = UnivariateSolverUtils.bracket(yawReached, dt0,
  217.                                                          dtMin, dtMax, fullTurn / 100, 1.0, 100);
  218.         if (yawReached.value(bracket[0]) <= 0.0) {
  219.             // we have bracketed the wrong crossing
  220.             bracket = UnivariateSolverUtils.bracket(yawReached, 0.5 * (bracket[0] + bracket[1] + fullTurn),
  221.                                                     bracket[1], bracket[1] + fullTurn, fullTurn / 100, 1.0, 100);
  222.         }
  223.         final double dt = new BracketingNthOrderBrentSolver(1.0e-3, 5).
  224.                           solve(100, yawReached, bracket[0], bracket[1]);
  225.         turnSpan.updateEnd(date.shiftedBy(dt), date);

  226.         return dt > 0.0;

  227.     }

  228.     /** Set up the midnight/noon turn region.
  229.      * @param cosNight limit cosine for the midnight turn
  230.      * @param cosNoon limit cosine for the noon turn
  231.      * @return true if spacecraft is in the midnight/noon turn region
  232.      */
  233.     public boolean setUpTurnRegion(final double cosNight, final double cosNoon) {
  234.         this.cNight = cosNight;
  235.         this.cNoon  = cosNoon;

  236.         if (svbCos < cNight || svbCos > cNoon) {
  237.             // we are within turn triggering zone
  238.             return true;
  239.         } else {
  240.             // we are outside of turn triggering zone,
  241.             // but we may still be trying to recover nominal attitude at the end of a turn
  242.             return inTurnTimeRange();
  243.         }

  244.     }

  245.     /** Get the relative orbit angle to turn center.
  246.      * @return relative orbit angle to turn center
  247.      */
  248.     public UnivariateDerivative2 getDeltaDS() {
  249.         return delta;
  250.     }

  251.     /** Get the orbit angle since solar midnight.
  252.      * @return orbit angle since solar midnight
  253.      */
  254.     public double getOrbitAngleSinceMidnight() {
  255.         final double absAngle = inOrbitPlaneAbsoluteAngle(FastMath.PI - FastMath.acos(svbCos));
  256.         return morning ? absAngle : -absAngle;
  257.     }

  258.     /** Check if spacecraft is in the half orbit closest to Sun.
  259.      * @return true if spacecraft is in the half orbit closest to Sun
  260.      */
  261.     public boolean inSunSide() {
  262.         return svbCos > 0;
  263.     }

  264.     /** Get yaw at turn start.
  265.      * @param sunBeta Sun elevation above orbital plane
  266.      * (it <em>may</em> be different from {@link #getBeta()} in
  267.      * some special cases)
  268.      * @return yaw at turn start
  269.      */
  270.     public double getYawStart(final double sunBeta) {
  271.         final double halfSpan = 0.5 * turnSpan.getTurnDuration() * muRate;
  272.         return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos));
  273.     }

  274.     /** Get yaw at turn end.
  275.      * @param sunBeta Sun elevation above orbital plane
  276.      * (it <em>may</em> be different from {@link #getBeta()} in
  277.      * some special cases)
  278.      * @return yaw at turn end
  279.      */
  280.     public double getYawEnd(final double sunBeta) {
  281.         final double halfSpan = 0.5 * turnSpan.getTurnDuration() * muRate;
  282.         return computePhi(sunBeta, FastMath.copySign(halfSpan, -svbCos));
  283.     }

  284.     /** Compute yaw rate.
  285.      * @param sunBeta Sun elevation above orbital plane
  286.      * (it <em>may</em> be different from {@link #getBeta()} in
  287.      * some special cases)
  288.      * @return yaw rate
  289.      */
  290.     public double yawRate(final double sunBeta) {
  291.         return (getYawEnd(sunBeta) - getYawStart(sunBeta)) / turnSpan.getTurnDuration();
  292.     }

  293.     /** Get the spacecraft angular velocity.
  294.      * @return spacecraft angular velocity
  295.      */
  296.     public double getMuRate() {
  297.         return muRate;
  298.     }

  299.     /** Project a spacecraft/Sun angle into orbital plane.
  300.      * <p>
  301.      * This method is intended to find the limits of the noon and midnight
  302.      * turns in orbital plane. The return angle is always positive. The
  303.      * correct sign to apply depends on the spacecraft being before or
  304.      * after turn middle point.
  305.      * </p>
  306.      * @param angle spacecraft/Sun angle (or spacecraft/opposite-of-Sun)
  307.      * @return angle projected into orbital plane, always positive
  308.      */
  309.     public double inOrbitPlaneAbsoluteAngle(final double angle) {
  310.         return FastMath.acos(FastMath.cos(angle) / FastMath.cos(beta(getDate())));
  311.     }

  312.     /** Compute yaw.
  313.      * @param sunBeta Sun elevation above orbital plane
  314.      * (it <em>may</em> be different from {@link #getBeta()} in
  315.      * some special cases)
  316.      * @param inOrbitPlaneAngle in orbit angle between spacecraft
  317.      * and Sun (or opposite of Sun) projection
  318.      * @return yaw angle
  319.      */
  320.     public double computePhi(final double sunBeta, final double inOrbitPlaneAngle) {
  321.         return FastMath.atan2(-FastMath.tan(sunBeta), FastMath.sin(inOrbitPlaneAngle));
  322.     }

  323.     /** Set turn half span and compute corresponding turn time range.
  324.      * @param halfSpan half span of the turn region, as an angle in orbit plane
  325.      * @param endMargin margin in seconds after turn end
  326.      */
  327.     public void setHalfSpan(final double halfSpan, final double endMargin) {

  328.         final AbsoluteDate start = date.shiftedBy((delta.getValue() - halfSpan) / muRate);
  329.         final AbsoluteDate end   = date.shiftedBy((delta.getValue() + halfSpan) / muRate);
  330.         final AbsoluteDate estimationDate = getDate();

  331.         if (turnSpan == null) {
  332.             turnSpan = new TurnSpan(start, end, estimationDate, endMargin);
  333.         } else {
  334.             turnSpan.updateStart(start, estimationDate);
  335.             turnSpan.updateEnd(end, estimationDate);
  336.         }
  337.     }

  338.     /** Check if context is within turn range.
  339.      * @return true if context is within range extended by end margin
  340.      */
  341.     public boolean inTurnTimeRange() {
  342.         return turnSpan != null && turnSpan.inTurnTimeRange(getDate());
  343.     }

  344.     /** Get elapsed time since turn start.
  345.      * @return elapsed time from turn start to current date
  346.      */
  347.     public double timeSinceTurnStart() {
  348.         return getDate().durationFrom(turnSpan.getTurnStartDate());
  349.     }

  350.     /** Generate an attitude with turn-corrected yaw.
  351.      * @param yaw yaw value to apply
  352.      * @param yawDot yaw first time derivative
  353.      * @return attitude with specified yaw
  354.      */
  355.     public TimeStampedAngularCoordinates turnCorrectedAttitude(final double yaw, final double yawDot) {
  356.         return turnCorrectedAttitude(new UnivariateDerivative2(yaw, yawDot, 0.0));
  357.     }

  358.     /** Generate an attitude with turn-corrected yaw.
  359.      * @param yaw yaw value to apply
  360.      * @return attitude with specified yaw
  361.      */
  362.     public TimeStampedAngularCoordinates turnCorrectedAttitude(final UnivariateDerivative2 yaw) {

  363.         // Earth pointing (Z aligned with position) with linear yaw (momentum with known cos/sin in the X/Y plane)
  364.         final Vector3D      p             = svPV.getPosition();
  365.         final Vector3D      v             = svPV.getVelocity();
  366.         final Vector3D      a             = svPV.getAcceleration();
  367.         final double        r2            = p.getNormSq();
  368.         final double        r             = FastMath.sqrt(r2);
  369.         final Vector3D      keplerianJerk = new Vector3D(-3 * Vector3D.dotProduct(p, v) / r2, a, -a.getNorm() / r, v);
  370.         final PVCoordinates velocity      = new PVCoordinates(v, a, keplerianJerk);
  371.         final PVCoordinates momentum      = PVCoordinates.crossProduct(svPV, velocity);

  372.         final FieldSinCos<UnivariateDerivative2> sc = FastMath.sinCos(yaw);
  373.         final UnivariateDerivative2 c = sc.cos().negate();
  374.         final UnivariateDerivative2 s = sc.sin().negate();
  375.         final Vector3D m0 = new Vector3D(s.getValue(),              c.getValue(),              0.0);
  376.         final Vector3D m1 = new Vector3D(s.getPartialDerivative(1), c.getPartialDerivative(1), 0.0);
  377.         final Vector3D m2 = new Vector3D(s.getPartialDerivative(2), c.getPartialDerivative(2), 0.0);
  378.         return new TimeStampedAngularCoordinates(date,
  379.                                                  svPV.normalize(), momentum.normalize(),
  380.                                                  MINUS_Z_PV, new PVCoordinates(m0, m1, m2),
  381.                                                  1.0e-9);

  382.     }

  383.     /** Compute Orbit Normal (ON) yaw.
  384.      * @return Orbit Normal yaw, using inertial frame as reference
  385.      */
  386.     public TimeStampedAngularCoordinates orbitNormalYaw() {
  387.         final Transform t = LOFType.LVLH_CCSDS.transformFromInertial(date, pvProv.getPVCoordinates(date, inertialFrame));
  388.         return new TimeStampedAngularCoordinates(date,
  389.                                                  t.getRotation(),
  390.                                                  t.getRotationRate(),
  391.                                                  t.getRotationAcceleration());
  392.     }

  393. }