Program Listing for File gnss_attitude.cc

Return to documentation for file (agents/gnss_attitude.cc)

#include "lupnt/agents/gnss_attitude.h"

#include "lupnt/agents/gnss_yaw_steering.h"
#include "lupnt/numerics/math_utils.h"

namespace lupnt {

  GnssAttitude::GnssAttitude(const Vec3& r_sat_eci, const Vec3& r_sun_eci) {
    Update(r_sat_eci, r_sun_eci);
  }

  void GnssAttitude::Compute(const Vec3& r_sat_eci, const Vec3& r_sun_eci, Vec3& ex, Vec3& ey,
                             Vec3& ez) {
    // GNSS satellite attitude frame (nadir / sun cross-track / along-track),
    // mirrors the attitude computation in `GNSSMeas.setup_measurements`:
    //   ez = normalize(-r_sat)                                (nadir)
    //   ey = normalize(cross(ez, normalize(r_sun - r_sat)))   (cross-track, toward Sun)
    //   ex = normalize(cross(ey, ez))                         (along-track, completes triad)
    ez = (-r_sat_eci).normalized();
    Vec3 e_to_sun = (r_sun_eci - r_sat_eci).normalized();
    ey = ez.cross(e_to_sun).normalized();
    ex = ey.cross(ez).normalized();
  }

  void GnssAttitude::ComputeFromYawAngle(const Vec3& r_sat_eci, const Vec3& v_sat_eci,
                                         Real yaw_angle, Vec3& ex, Vec3& ey, Vec3& ez) {
    // Orbital reference frame (the body frame at yaw angle phi = 0):
    //   ez     = normalize(-r_sat)              (nadir)
    //   ex_ref = normalize(v_sat)               (along-track)
    //   ey_ref = cross(ez, ex_ref)              (completes the right-handed triad)
    // The actual frame (ex, ey) is (ex_ref, ey_ref) rotated by `yaw_angle`
    // about the nadir axis ez:
    //   ex =  cos(phi) * ex_ref + sin(phi) * ey_ref
    //   ey = -sin(phi) * ex_ref + cos(phi) * ey_ref
    ez = (-r_sat_eci).normalized();
    Vec3 ex_ref = v_sat_eci.normalized();
    Vec3 ey_ref = ez.cross(ex_ref);
    Real c = cos(yaw_angle);
    Real s = sin(yaw_angle);
    ex = (c * ex_ref + s * ey_ref).normalized();
    ey = (-s * ex_ref + c * ey_ref).normalized();
  }

  void GnssAttitude::Compute(const Vec3& r_sat_eci, const Vec3& v_sat_eci, const Vec3& r_sun_eci,
                             Vec3& ex, Vec3& ey, Vec3& ez) {
    // Drive the attitude frame from the documented nominal yaw-steering law
    // (Kouba, 2009; Eq. 1 of Cheng et al., 2025): compute the Sun-elevation
    // angle `beta` and orbit angle `mu`, evaluate the nominal yaw angle
    // `phi = NominalYawAngle(beta, mu)`, and build the frame by rotating the
    // orbital reference frame about nadir by `phi` (see `ComputeFromYawAngle`).
    // This reproduces exactly the Sun-pointing frame of the geometric
    // `Compute(r_sat_eci, r_sun_eci, ...)` overload above (the nominal yaw law
    // is, by construction, the yaw angle that keeps the solar panels
    // Sun-pointing) -- verified numerically in `test_gnss_attitude.cc`.
    Real beta = GnssYawSteering::BetaAngle(r_sat_eci, v_sat_eci, r_sun_eci);
    Real mu = GnssYawSteering::OrbitAngle(r_sat_eci, v_sat_eci, r_sun_eci);
    Real phi = GnssYawSteering::NominalYawAngle(beta, mu);
    ComputeFromYawAngle(r_sat_eci, v_sat_eci, phi, ex, ey, ez);
  }

  void GnssAttitude::Update(const Vec3& r_sat_eci, const Vec3& r_sun_eci) {
    Compute(r_sat_eci, r_sun_eci, ex_, ey_, ez_);
  }

  void GnssAttitude::Update(const Vec3& r_sat_eci, const Vec3& v_sat_eci, const Vec3& r_sun_eci) {
    Compute(r_sat_eci, v_sat_eci, r_sun_eci, ex_, ey_, ez_);
  }

  Mat3 GnssAttitude::GetRotationMatrix() const {
    Mat3 R;
    R.col(0) = ex_;
    R.col(1) = ey_;
    R.col(2) = ez_;
    return R;
  }

  void GnssAttitude::GetAngles(const Vec3& u, Real& theta, Real& phi) const {
    phi = safe_acos(u.dot(ez_));
    theta = atan2(u.dot(ey_), u.dot(ex_));
  }

}  // namespace lupnt