GNSSFieldAttitudeContext.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.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.analysis.UnivariateFunction;
  21. import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
  22. import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
  23. import org.hipparchus.analysis.solvers.UnivariateSolverUtils;
  24. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  25. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  26. import org.hipparchus.util.FastMath;
  27. import org.hipparchus.util.FieldSinCos;
  28. import org.hipparchus.util.SinCos;
  29. import org.orekit.frames.FieldTransform;
  30. import org.orekit.frames.Frame;
  31. import org.orekit.frames.LOFType;
  32. import org.orekit.time.AbsoluteDate;
  33. import org.orekit.time.FieldAbsoluteDate;
  34. import org.orekit.time.FieldTimeStamped;
  35. import org.orekit.utils.ExtendedPVCoordinatesProvider;
  36. import org.orekit.utils.FieldPVCoordinates;
  37. import org.orekit.utils.FieldPVCoordinatesProvider;
  38. import org.orekit.utils.PVCoordinates;
  39. import org.orekit.utils.TimeStampedFieldAngularCoordinates;
  40. import org.orekit.utils.TimeStampedFieldPVCoordinates;

  41. /**
  42.  * Boilerplate computations for GNSS attitude.
  43.  *
  44.  * <p>
  45.  * This class is intended to hold throw-away data pertaining to <em>one</em> call
  46.  * to {@link GNSSAttitudeProvider#getAttitude(org.orekit.utils.FieldPVCoordinatesProvider,
  47.  * org.orekit.time.FieldAbsoluteDate, org.orekit.frames.Frame)) getAttitude}. It allows
  48.  * the various {@link GNSSAttitudeProvider} implementations to be immutable as they
  49.  * do not store any state, and hence to be thread-safe and reentrant.
  50.  * </p>
  51.  *
  52.  * @author Luc Maisonobe
  53.  * @since 9.2
  54.  */
  55. class GNSSFieldAttitudeContext<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T> {

  56.     /** Constant Y axis. */
  57.     private static final PVCoordinates PLUS_Y_PV =
  58.             new PVCoordinates(Vector3D.PLUS_J, Vector3D.ZERO, Vector3D.ZERO);

  59.     /** Constant Z axis. */
  60.     private static final PVCoordinates MINUS_Z_PV =
  61.             new PVCoordinates(Vector3D.MINUS_K, Vector3D.ZERO, Vector3D.ZERO);

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

  64.     /** Constant Y axis. */
  65.     private final FieldPVCoordinates<T> plusY;

  66.     /** Constant Z axis. */
  67.     private final FieldPVCoordinates<T> minusZ;

  68.     /** Context date. */
  69.     private final AbsoluteDate dateDouble;

  70.     /** Current date. */
  71.     private final FieldAbsoluteDate<T> date;

  72.     /** Provider for Sun position. */
  73.     private final ExtendedPVCoordinatesProvider sun;

  74.     /** Provider for spacecraft position. */
  75.     private final FieldPVCoordinatesProvider<T> pvProv;

  76.     /** Spacecraft position at central date.
  77.      * @since 12.0
  78.      */
  79.     private final TimeStampedFieldPVCoordinates<T> svPV;

  80.     /** Sun position at central date.
  81.      * @since 12.0
  82.      */
  83.     private final TimeStampedFieldPVCoordinates<T> sunPV;

  84.     /** Inertial frame where velocity are computed. */
  85.     private final Frame inertialFrame;

  86.     /** Cosine of the angle between spacecraft and Sun direction. */
  87.     private final T svbCos;

  88.     /** Morning/Evening half orbit indicator. */
  89.     private final boolean morning;

  90.     /** Relative orbit angle to turn center. */
  91.     private final FieldUnivariateDerivative2<T> delta;

  92.     /** Sun elevation at center.
  93.      * @since 12.0
  94.      */
  95.     private final FieldUnivariateDerivative2<T> beta;

  96.     /** Spacecraft angular velocity. */
  97.     private T muRate;

  98.     /** Limit cosine for the midnight turn. */
  99.     private double cNight;

  100.     /** Limit cosine for the noon turn. */
  101.     private double cNoon;

  102.     /** Turn time data. */
  103.     private FieldTurnSpan<T> turnSpan;

  104.     /** Simple constructor.
  105.      * @param date current date
  106.      * @param sun provider for Sun position
  107.      * @param pvProv provider for spacecraft position
  108.      * @param inertialFrame inertial frame where velocity are computed
  109.      * @param turnSpan turn time data, if a turn has already been identified in the date neighborhood,
  110.      * null otherwise
  111.      */
  112.     GNSSFieldAttitudeContext(final FieldAbsoluteDate<T> date,
  113.                              final ExtendedPVCoordinatesProvider sun, final FieldPVCoordinatesProvider<T> pvProv,
  114.                              final Frame inertialFrame,  final FieldTurnSpan<T> turnSpan) {

  115.         final Field<T> field = date.getField();
  116.         plusY    = new FieldPVCoordinates<>(field, PLUS_Y_PV);
  117.         minusZ   = new FieldPVCoordinates<>(field, MINUS_Z_PV);

  118.         this.dateDouble    = date.toAbsoluteDate();
  119.         this.date          = date;
  120.         this.sun           = sun;
  121.         this.pvProv        = pvProv;
  122.         this.inertialFrame = inertialFrame;
  123.         this.sunPV         = sun.getPVCoordinates(date, inertialFrame);
  124.         this.svPV          = pvProv.getPVCoordinates(date, inertialFrame);
  125.         this.morning       = Vector3D.dotProduct(svPV.getVelocity().toVector3D(), sunPV.getPosition().toVector3D()) >= 0.0;
  126.         this.muRate        = svPV.getAngularVelocity().getNorm();
  127.         this.turnSpan      = turnSpan;

  128.         final FieldPVCoordinates<FieldUnivariateDerivative2<T>> sunPVD2 = sunPV.toUnivariateDerivative2PV();
  129.         final FieldPVCoordinates<FieldUnivariateDerivative2<T>> svPVD2  = svPV.toUnivariateDerivative2PV();
  130.         final FieldUnivariateDerivative2<T> svbCosD2  = FieldVector3D.dotProduct(sunPVD2.getPosition(), svPVD2.getPosition()).
  131.                                                         divide(sunPVD2.getPosition().getNorm().multiply(svPVD2.getPosition().getNorm()));
  132.         svbCos = svbCosD2.getValue();

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

  134.         final FieldUnivariateDerivative2<T> absDelta;
  135.         if (svbCos.getReal() <= 0) {
  136.             // night side
  137.             absDelta = FastMath.acos(svbCosD2.negate().divide(FastMath.cos(beta)));
  138.         } else {
  139.             // Sun side
  140.             absDelta = FastMath.acos(svbCosD2.divide(FastMath.cos(beta)));
  141.         }
  142.         delta = absDelta.copySign(absDelta.getPartialDerivative(1).negate());

  143.     }

  144.     /** Compute nominal yaw steering.
  145.      * @param d computation date
  146.      * @return nominal yaw steering
  147.      */
  148.     public TimeStampedFieldAngularCoordinates<T> nominalYaw(final FieldAbsoluteDate<T> d) {
  149.         final TimeStampedFieldPVCoordinates<T> pv = pvProv.getPVCoordinates(d, inertialFrame);
  150.         return new TimeStampedFieldAngularCoordinates<>(d,
  151.                                                         pv.normalize(),
  152.                                                         sun.getPVCoordinates(d, inertialFrame).crossProduct(pv).normalize(),
  153.                                                         minusZ,
  154.                                                         plusY,
  155.                                                         1.0e-9);
  156.     }

  157.     /** Compute Sun elevation.
  158.      * @param d computation date
  159.      * @return Sun elevation
  160.      */
  161.     public T beta(final FieldAbsoluteDate<T> d) {
  162.         final TimeStampedFieldPVCoordinates<T> pv = pvProv.getPVCoordinates(d, inertialFrame);
  163.         return FieldVector3D.angle(sun.getPosition(d, inertialFrame), pv.getMomentum()).
  164.                negate().
  165.                add(svPV.getPosition().getX().getPi().multiply(0.5));
  166.     }

  167.     /** Compute Sun elevation.
  168.      * @return Sun elevation
  169.      */
  170.     public FieldUnivariateDerivative2<T> betaD2() {
  171.         return beta;
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public FieldAbsoluteDate<T> getDate() {
  176.         return date;
  177.     }

  178.     /** Get the turn span.
  179.      * @return turn span, may be null if context is outside of turn
  180.      */
  181.     public FieldTurnSpan<T> getTurnSpan() {
  182.         return turnSpan;
  183.     }

  184.     /** Get the cosine of the angle between spacecraft and Sun direction.
  185.      * @return cosine of the angle between spacecraft and Sun direction.
  186.      */
  187.     public T getSVBcos() {
  188.         return svbCos;
  189.     }

  190.     /** Get a Sun elevation angle that does not change sign within the turn.
  191.      * <p>
  192.      * This method either returns the current beta or replaces it with the
  193.      * value at turn start, so the sign remains constant throughout the
  194.      * turn. As of 9.2, it is used for GPS, Glonass and Galileo.
  195.      * </p>
  196.      * @return secured Sun elevation angle
  197.      * @see #beta(FieldAbsoluteDate)
  198.      * @see #betaDS(FieldAbsoluteDate)
  199.      */
  200.     public T getSecuredBeta() {
  201.         return FastMath.abs(beta.getValue().getReal()) < BETA_SIGN_CHANGE_PROTECTION ?
  202.                beta(turnSpan.getTurnStartDate()) :
  203.                beta.getValue();
  204.     }

  205.     /** Check if a linear yaw model is still active or if we already reached target yaw.
  206.      * @param linearPhi value of the linear yaw model
  207.      * @param phiDot slope of the linear yaw model
  208.      * @return true if linear model is still active
  209.      */
  210.     public boolean linearModelStillActive(final T linearPhi, final T phiDot) {
  211.         final AbsoluteDate absDate = date.toAbsoluteDate();
  212.         final double dt0 = turnSpan.getTurnEndDate().durationFrom(date).getReal();
  213.         final UnivariateFunction yawReached = dt -> {
  214.             final AbsoluteDate  t       = absDate.shiftedBy(dt);
  215.             final Vector3D      pSun    = sun.getPosition(t, inertialFrame);
  216.             final PVCoordinates pv      = pvProv.getPVCoordinates(date.shiftedBy(dt), inertialFrame).toPVCoordinates();
  217.             final Vector3D      pSat    = pv.getPosition();
  218.             final Vector3D      targetX = Vector3D.crossProduct(pSat, Vector3D.crossProduct(pSun, pSat)).normalize();

  219.             final double        phi         = linearPhi.getReal() + dt * phiDot.getReal();
  220.             final SinCos        sc          = FastMath.sinCos(phi);
  221.             final Vector3D      pU          = pv.getPosition().normalize();
  222.             final Vector3D      mU          = pv.getMomentum().normalize();
  223.             final Vector3D      omega       = new Vector3D(-phiDot.getReal(), pU);
  224.             final Vector3D      currentX    = new Vector3D(-sc.sin(), mU, -sc.cos(), Vector3D.crossProduct(pU, mU));
  225.             final Vector3D      currentXDot = Vector3D.crossProduct(omega, currentX);

  226.             return Vector3D.dotProduct(targetX, currentXDot);
  227.         };
  228.         final double fullTurn = 2 * FastMath.PI / FastMath.abs(phiDot.getReal());
  229.         final double dtMin    = FastMath.min(turnSpan.getTurnStartDate().durationFrom(date).getReal(), dt0 - 60.0);
  230.         final double dtMax    = FastMath.max(dtMin + fullTurn, dt0 + 60.0);
  231.         double[] bracket = UnivariateSolverUtils.bracket(yawReached, dt0,
  232.                                                          dtMin, dtMax, fullTurn / 100, 1.0, 100);
  233.         if (yawReached.value(bracket[0]) <= 0.0) {
  234.             // we have bracketed the wrong crossing
  235.             bracket = UnivariateSolverUtils.bracket(yawReached, 0.5 * (bracket[0] + bracket[1] + fullTurn),
  236.                                                     bracket[1], bracket[1] + fullTurn, fullTurn / 100, 1.0, 100);
  237.         }
  238.         final double dt = new BracketingNthOrderBrentSolver(1.0e-3, 5).
  239.                           solve(100, yawReached, bracket[0], bracket[1]);
  240.         turnSpan.updateEnd(date.shiftedBy(dt), absDate);

  241.         return dt > 0.0;

  242.     }

  243.     /** Set up the midnight/noon turn region.
  244.      * @param cosNight limit cosine for the midnight turn
  245.      * @param cosNoon limit cosine for the noon turn
  246.      * @return true if spacecraft is in the midnight/noon turn region
  247.      */
  248.     public boolean setUpTurnRegion(final double cosNight, final double cosNoon) {
  249.         this.cNight = cosNight;
  250.         this.cNoon  = cosNoon;

  251.         if (svbCos.getReal() < cNight || svbCos.getReal() > cNoon) {
  252.             // we are within turn triggering zone
  253.             return true;
  254.         } else {
  255.             // we are outside of turn triggering zone,
  256.             // but we may still be trying to recover nominal attitude at the end of a turn
  257.             return inTurnTimeRange();
  258.         }

  259.     }

  260.     /** Get the relative orbit angle to turn center.
  261.      * @return relative orbit angle to turn center
  262.      */
  263.     public FieldUnivariateDerivative2<T> getDeltaDS() {
  264.         return delta;
  265.     }

  266.     /** Get the orbit angle since solar midnight.
  267.      * @return orbit angle since solar midnight
  268.      */
  269.     public T getOrbitAngleSinceMidnight() {
  270.         final T absAngle = inOrbitPlaneAbsoluteAngle(FastMath.acos(svbCos).negate().add(svbCos.getPi()));
  271.         return morning ? absAngle : absAngle.negate();
  272.     }

  273.     /** Check if spacecraft is in the half orbit closest to Sun.
  274.      * @return true if spacecraft is in the half orbit closest to Sun
  275.      */
  276.     public boolean inSunSide() {
  277.         return svbCos.getReal() > 0;
  278.     }

  279.     /** Get yaw at turn start.
  280.      * @param sunBeta Sun elevation above orbital plane
  281.      * (it <em>may</em> be different from {@link #getBeta()} in
  282.      * some special cases)
  283.      * @return yaw at turn start
  284.      */
  285.     public T getYawStart(final T sunBeta) {
  286.         final T halfSpan = turnSpan.getTurnDuration().multiply(muRate).multiply(0.5);
  287.         return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos));
  288.     }

  289.     /** Get yaw at turn end.
  290.      * @param sunBeta Sun elevation above orbital plane
  291.      * (it <em>may</em> be different from {@link #getBeta()} in
  292.      * some special cases)
  293.      * @return yaw at turn end
  294.      */
  295.     public T getYawEnd(final T sunBeta) {
  296.         final T halfSpan = turnSpan.getTurnDuration().multiply(muRate).multiply(0.5);
  297.         return computePhi(sunBeta, FastMath.copySign(halfSpan, svbCos.negate()));
  298.     }

  299.     /** Compute yaw rate.
  300.      * @param sunBeta Sun elevation above orbital plane
  301.      * (it <em>may</em> be different from {@link #getBeta()} in
  302.      * some special cases)
  303.      * @return yaw rate
  304.      */
  305.     public T yawRate(final T sunBeta) {
  306.         return getYawEnd(sunBeta).subtract(getYawStart(sunBeta)).divide(turnSpan.getTurnDuration());
  307.     }

  308.     /** Get the spacecraft angular velocity.
  309.      * @return spacecraft angular velocity
  310.      */
  311.     public T getMuRate() {
  312.         return muRate;
  313.     }

  314.     /** Project a spacecraft/Sun angle into orbital plane.
  315.      * <p>
  316.      * This method is intended to find the limits of the noon and midnight
  317.      * turns in orbital plane. The return angle is always positive. The
  318.      * correct sign to apply depends on the spacecraft being before or
  319.      * after turn middle point.
  320.      * </p>
  321.      * @param angle spacecraft/Sun angle (or spacecraft/opposite-of-Sun)
  322.      * @return angle projected into orbital plane, always positive
  323.      */
  324.     public T inOrbitPlaneAbsoluteAngle(final T angle) {
  325.         return FastMath.acos(FastMath.cos(angle).divide(FastMath.cos(beta(getDate()))));
  326.     }

  327.     /** Compute yaw.
  328.      * @param sunBeta Sun elevation above orbital plane
  329.      * (it <em>may</em> be different from {@link #getBeta()} in
  330.      * some special cases)
  331.      * @param inOrbitPlaneAngle in orbit angle between spacecraft
  332.      * and Sun (or opposite of Sun) projection
  333.      * @return yaw angle
  334.      */
  335.     public T computePhi(final T sunBeta, final T inOrbitPlaneAngle) {
  336.         return FastMath.atan2(FastMath.tan(sunBeta).negate(), FastMath.sin(inOrbitPlaneAngle));
  337.     }

  338.     /** Set turn half span and compute corresponding turn time range.
  339.      * @param halfSpan half span of the turn region, as an angle in orbit plane
  340.      * @param endMargin margin in seconds after turn end
  341.      */
  342.     public void setHalfSpan(final T halfSpan, final double endMargin) {
  343.         final FieldAbsoluteDate<T> start = date.shiftedBy(delta.getValue().subtract(halfSpan).divide(muRate));
  344.         final FieldAbsoluteDate<T> end   = date.shiftedBy(delta.getValue().add(halfSpan).divide(muRate));
  345.         final AbsoluteDate estimationDate = getDate().toAbsoluteDate();
  346.         if (turnSpan == null) {
  347.             turnSpan = new FieldTurnSpan<>(start, end, estimationDate, endMargin);
  348.         } else {
  349.             turnSpan.updateStart(start, estimationDate);
  350.             turnSpan.updateEnd(end, estimationDate);
  351.         }
  352.     }

  353.     /** Check if context is within turn range.
  354.      * @return true if context is within range extended by end margin
  355.      */
  356.     public boolean inTurnTimeRange() {
  357.         return turnSpan != null && turnSpan.inTurnTimeRange(dateDouble);
  358.     }

  359.     /** Get elapsed time since turn start.
  360.      * @return elapsed time from turn start to current date
  361.      */
  362.     public T timeSinceTurnStart() {
  363.         return getDate().durationFrom(turnSpan.getTurnStartDate());
  364.     }

  365.     /** Generate an attitude with turn-corrected yaw.
  366.      * @param yaw yaw value to apply
  367.      * @param yawDot yaw first time derivative
  368.      * @return attitude with specified yaw
  369.      */
  370.     public TimeStampedFieldAngularCoordinates<T> turnCorrectedAttitude(final T yaw, final T yawDot) {
  371.         return turnCorrectedAttitude(new FieldUnivariateDerivative2<>(yaw, yawDot, yaw.getField().getZero()));
  372.     }

  373.     /** Generate an attitude with turn-corrected yaw.
  374.      * @param yaw yaw value to apply
  375.      * @return attitude with specified yaw
  376.      */
  377.     public TimeStampedFieldAngularCoordinates<T> turnCorrectedAttitude(final FieldUnivariateDerivative2<T> yaw) {

  378.         // Earth pointing (Z aligned with position) with linear yaw (momentum with known cos/sin in the X/Y plane)
  379.         final FieldVector3D<T>      p             = svPV.getPosition();
  380.         final FieldVector3D<T>      v             = svPV.getVelocity();
  381.         final FieldVector3D<T>      a             = svPV.getAcceleration();
  382.         final T                     r2            = p.getNormSq();
  383.         final T                     r             = FastMath.sqrt(r2);
  384.         final FieldVector3D<T>      keplerianJerk = new FieldVector3D<>(FieldVector3D.dotProduct(p, v).multiply(-3).divide(r2), a,
  385.                                                                         a.getNorm().negate().divide(r), v);
  386.         final FieldPVCoordinates<T> velocity      = new FieldPVCoordinates<>(v, a, keplerianJerk);
  387.         final FieldPVCoordinates<T> momentum      = svPV.crossProduct(velocity);

  388.         final FieldSinCos<FieldUnivariateDerivative2<T>> sc = FastMath.sinCos(yaw);
  389.         final FieldUnivariateDerivative2<T> c = sc.cos().negate();
  390.         final FieldUnivariateDerivative2<T> s = sc.sin().negate();
  391.         final T                             z = yaw.getValueField().getZero();
  392.         final FieldVector3D<T> m0 = new FieldVector3D<>(s.getValue(),              c.getValue(),              z);
  393.         final FieldVector3D<T> m1 = new FieldVector3D<>(s.getPartialDerivative(1), c.getPartialDerivative(1), z);
  394.         final FieldVector3D<T> m2 = new FieldVector3D<>(s.getPartialDerivative(2), c.getPartialDerivative(2), z);
  395.         return new TimeStampedFieldAngularCoordinates<>(date,
  396.                                                         svPV.normalize(), momentum.normalize(),
  397.                                                         minusZ, new FieldPVCoordinates<>(m0, m1, m2),
  398.                                                         1.0e-9);

  399.     }

  400.     /** Compute Orbit Normal (ON) yaw.
  401.      * @return Orbit Normal yaw, using inertial frame as reference
  402.      */
  403.     public TimeStampedFieldAngularCoordinates<T> orbitNormalYaw() {
  404.         final FieldTransform<T> t = LOFType.LVLH_CCSDS.transformFromInertial(date, pvProv.getPVCoordinates(date, inertialFrame));
  405.         return new TimeStampedFieldAngularCoordinates<>(date,
  406.                                                         t.getRotation(),
  407.                                                         t.getRotationRate(),
  408.                                                         t.getRotationAcceleration());
  409.     }

  410. }