GPSBlockIIA.java

  1. /* Copyright 2002-2025 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.Field;
  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.util.FastMath;
  21. import org.orekit.frames.Frame;
  22. import org.orekit.time.AbsoluteDate;
  23. import org.orekit.utils.ExtendedPositionProvider;
  24. import org.orekit.utils.TimeStampedAngularCoordinates;
  25. import org.orekit.utils.TimeStampedFieldAngularCoordinates;

  26. /**
  27.  * Attitude providers for GPS block IIA navigation satellites.
  28.  * <p>
  29.  * This class is based on the May 2017 version of J. Kouba eclips.f
  30.  * subroutine available at <a href="http://acc.igs.org/orbits">IGS Analysis
  31.  * Center Coordinator site</a>. The eclips.f code itself is not used ; its
  32.  * hard-coded data are used and its low level models are used, but the
  33.  * structure of the code and the API have been completely rewritten.
  34.  * </p>
  35.  * @author J. Kouba original fortran routine
  36.  * @author Luc Maisonobe Java translation
  37.  * @since 9.2
  38.  */
  39. public class GPSBlockIIA extends AbstractGNSSAttitudeProvider {

  40.     /** Default yaw bias (rad). */
  41.     public static final double DEFAULT_YAW_BIAS = FastMath.toRadians(0.5);

  42.     /** Satellite-Sun angle limit for a midnight turn maneuver. */
  43.     private static final double NIGHT_TURN_LIMIT = FastMath.toRadians(180.0 - 13.25);

  44.     /** Margin on turn end. */
  45.     private static final double END_MARGIN = 1800.0;

  46.     /** Default yaw rates for all spacecrafts in radians per seconds (indexed by prnNumber, hence entry 0 is unused). */
  47.     private static final double[] DEFAULT_YAW_RATES = new double[] {
  48.         Double.NaN,  // unused entry 0
  49.         FastMath.toRadians(0.1211), FastMath.toRadians(0.1339), FastMath.toRadians(0.1230), FastMath.toRadians(0.1233),
  50.         FastMath.toRadians(0.1180), FastMath.toRadians(0.1266), FastMath.toRadians(0.1269), FastMath.toRadians(0.1033),
  51.         FastMath.toRadians(0.1278), FastMath.toRadians(0.0978), FastMath.toRadians(0.2000), FastMath.toRadians(0.1990),
  52.         FastMath.toRadians(0.2000), FastMath.toRadians(0.0815), FastMath.toRadians(0.1303), FastMath.toRadians(0.0838),
  53.         FastMath.toRadians(0.1401), FastMath.toRadians(0.1069), FastMath.toRadians(0.0980), FastMath.toRadians(0.1030),
  54.         FastMath.toRadians(0.1366), FastMath.toRadians(0.1025), FastMath.toRadians(0.1140), FastMath.toRadians(0.1089),
  55.         FastMath.toRadians(0.1001), FastMath.toRadians(0.1227), FastMath.toRadians(0.1194), FastMath.toRadians(0.1260),
  56.         FastMath.toRadians(0.1228), FastMath.toRadians(0.1165), FastMath.toRadians(0.0969), FastMath.toRadians(0.1140)
  57.     };

  58.     /** Yaw rate for current spacecraft. */
  59.     private final double yawRate;

  60.     /** Yaw bias. */
  61.     private final double yawBias;

  62.     /** Simple constructor.
  63.      * @param yawRate yaw rate to use in radians per seconds (typically {@link #getDefaultYawRate(int) GPSBlockIIA.getDefaultYawRate(prnNumber)})
  64.      * @param yawBias yaw bias to use (rad) (typicall {@link #DEFAULT_YAW_BIAS})
  65.      * @param validityStart start of validity for this provider
  66.      * @param validityEnd end of validity for this provider
  67.      * @param sun provider for Sun position
  68.      * @param inertialFrame inertial frame where velocity are computed
  69.      * @since 9.3
  70.      */
  71.     public GPSBlockIIA(final double yawRate, final double yawBias,
  72.                        final AbsoluteDate validityStart, final AbsoluteDate validityEnd,
  73.                        final ExtendedPositionProvider sun, final Frame inertialFrame) {
  74.         super(validityStart, validityEnd, sun, inertialFrame);
  75.         this.yawRate = yawRate;
  76.         this.yawBias = yawBias;
  77.     }

  78.     /** Get the default yaw rate for a satellite.
  79.      * @param prnNumber satellite PRN
  80.      * @return default yaw rate for the specified satellite
  81.      * @since 10.0
  82.      */
  83.     public static double getDefaultYawRate(final int prnNumber) {
  84.         return DEFAULT_YAW_RATES[prnNumber];
  85.     }

  86.     /** {@inheritDoc} */
  87.     @Override
  88.     protected TimeStampedAngularCoordinates correctedYaw(final GNSSAttitudeContext context) {

  89.         // noon beta angle limit from yaw rate
  90.         final double aNoon  = FastMath.atan(context.getMuRate() / yawRate);
  91.         final double aNight = NIGHT_TURN_LIMIT;
  92.         final double cNoon  = FastMath.cos(aNoon);
  93.         final double cNight = FastMath.cos(aNight);

  94.         if (context.setUpTurnRegion(cNight, cNoon)) {

  95.             final double absBeta = FastMath.abs(context.beta(context.getDate()));
  96.             context.setHalfSpan(context.inSunSide() ?
  97.                                 absBeta * FastMath.sqrt(aNoon / absBeta - 1.0) :
  98.                                 context.inOrbitPlaneAbsoluteAngle(aNight - FastMath.PI),
  99.                                 END_MARGIN);
  100.             if (context.inTurnTimeRange()) {

  101.                 // we need to ensure beta sign does not change during the turn
  102.                 final double beta     = context.getSecuredBeta();
  103.                 final double phiStart = context.getYawStart(beta);
  104.                 final double dtStart  = context.timeSinceTurnStart();
  105.                 final double linearPhi;
  106.                 final double phiDot;
  107.                 if (context.inSunSide()) {
  108.                     // noon turn
  109.                     if (beta > 0 && beta < yawBias) {
  110.                         // noon turn problem for small positive beta in block IIA
  111.                         // rotation is in the wrong direction for these spacecrafts
  112.                         phiDot    = FastMath.copySign(yawRate, beta);
  113.                         linearPhi = phiStart + phiDot * dtStart;
  114.                     } else {
  115.                         // regular noon turn
  116.                         phiDot    = -FastMath.copySign(yawRate, beta);
  117.                         linearPhi = phiStart + phiDot * dtStart;
  118.                     }
  119.                 } else {
  120.                     // midnight turn
  121.                     phiDot    = yawRate;
  122.                     linearPhi = phiStart + phiDot * dtStart;
  123.                 }

  124.                 if (context.linearModelStillActive(linearPhi, phiDot)) {
  125.                     // we are still in the linear model phase
  126.                     return context.turnCorrectedAttitude(linearPhi, phiDot);
  127.                 }

  128.             }

  129.         }

  130.         // in nominal yaw mode
  131.         return context.nominalYaw(context.getDate());

  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     protected <T extends CalculusFieldElement<T>> TimeStampedFieldAngularCoordinates<T> correctedYaw(final GNSSFieldAttitudeContext<T> context) {

  136.         final Field<T> field = context.getDate().getField();

  137.         // noon beta angle limit from yaw rate
  138.         final T      aNoon  = FastMath.atan(context.getMuRate().divide(yawRate));
  139.         final T      aNight = field.getZero().newInstance(NIGHT_TURN_LIMIT);
  140.         final double cNoon  = FastMath.cos(aNoon.getReal());
  141.         final double cNight = FastMath.cos(aNight.getReal());

  142.         if (context.setUpTurnRegion(cNight, cNoon)) {

  143.             final T absBeta = FastMath.abs(context.beta(context.getDate()));
  144.             context.setHalfSpan(context.inSunSide() ?
  145.                                 absBeta.multiply(FastMath.sqrt(aNoon.divide(absBeta).subtract(1.0))) :
  146.                                 context.inOrbitPlaneAbsoluteAngle(aNight.subtract(aNoon.getPi())),
  147.                                 END_MARGIN);
  148.             if (context.inTurnTimeRange()) {

  149.                 // we need to ensure beta sign does not change during the turn
  150.                 final T beta     = context.getSecuredBeta();
  151.                 final T phiStart = context.getYawStart(beta);
  152.                 final T dtStart  = context.timeSinceTurnStart();
  153.                 final T linearPhi;
  154.                 final T phiDot;
  155.                 if (context.inSunSide()) {
  156.                     // noon turn
  157.                     if (beta.getReal() > 0 && beta.getReal() < yawBias) {
  158.                         // noon turn problem for small positive beta in block IIA
  159.                         // rotation is in the wrong direction for these spacecrafts
  160.                         phiDot    = field.getZero().newInstance(FastMath.copySign(yawRate, beta.getReal()));
  161.                         linearPhi = phiStart.add(phiDot.multiply(dtStart));
  162.                     } else {
  163.                         // regular noon turn
  164.                         phiDot    = field.getZero().newInstance(-FastMath.copySign(yawRate, beta.getReal()));
  165.                         linearPhi = phiStart.add(phiDot.multiply(dtStart));
  166.                     }
  167.                 } else {
  168.                     // midnight turn
  169.                     phiDot    = field.getZero().newInstance(yawRate);
  170.                     linearPhi = phiStart.add(phiDot.multiply(dtStart));
  171.                 }

  172.                 if (context.linearModelStillActive(linearPhi, phiDot)) {
  173.                     // we are still in the linear model phase
  174.                     return context.turnCorrectedAttitude(linearPhi, phiDot);
  175.                 }

  176.             }

  177.         }

  178.         // in nominal yaw mode
  179.         return context.nominalYaw(context.getDate());

  180.     }

  181. }