Changeset 10946 in ntrip for trunk/BNC/src/bncantex.cpp


Ignore:
Timestamp:
Jun 24, 2026, 4:39:53 PM (13 hours ago)
Author:
stuerze
Message:

added GLONASS-M yaw-fixed attitude model for the APC/CoM offset applied when saving SP3 files, reducing along-track/cross-track errors near the orbit noon/midnight points

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/src/bncantex.cpp

    r10619 r10946  
    4040
    4141#include <iostream>
     42#include <cmath>
    4243#include <newmatio.h>
    4344
     
    330331}
    331332
     333// GLONASS Yaw Angle (Sun-pointing law overridden near noon/midnight when
     334// the satellite cannot mechanically keep up, following the GLONASS-M
     335// "yaw-fixed" behaviour described in Dilssner, Springer, Flohrer, Dow
     336// (2011), "The GLONASS-M satellite yaw-attitude model", Advances in Space
     337// Research 47(1), 160-171.
     338//
     339// Unlike GPS/Galileo/BeiDou, which are commonly approximated by the
     340// nominal Sun-pointing yaw-steering law of Bar-Sever (1996) at all times,
     341// GLONASS-M has been found to stop tracking that law and hold a constant
     342// (frozen) yaw angle whenever the required yaw rate would exceed the
     343// satellite's slew capability - which happens close to the orbit
     344// noon/midnight points whenever the Sun's elevation above the orbital
     345// plane (the "beta" angle) is small. The maximum yaw rate used below
     346// (0.25 deg/s) and the general approach follow that paper; satellite
     347// telemetry was not available to validate the exact rate against this
     348// installation's GLONASS satellites, so it should be checked against
     349// independently-known attitude/orbit residuals if high accuracy matters.
     350////////////////////////////////////////////////////////////////////////////
     351double bncAntex::glonassYawAngle(const QString& prn, const ColumnVector& xSat,
     352                                  const ColumnVector& vSat,
     353                                  const ColumnVector& xSun) {
     354
     355  const double MAX_YAW_RATE = 0.25 * M_PI / 180.0; // [rad/s], approximate
     356
     357  // Inertial-consistent velocity (xSat, vSat are Earth-fixed; remove the
     358  // Earth-rotation contribution so that the orbit normal below is not
     359  // contaminated by it)
     360  // -------------------------------------------------------------------
     361  ColumnVector Omega(3); Omega(1) = 0.0; Omega(2) = 0.0; Omega(3) = t_CST::omega;
     362  ColumnVector vInert = vSat + crossproduct(Omega, xSat);
     363
     364  // Orbit normal and instantaneous orbital rate from r x v (exact, valid
     365  // for any Keplerian orbit, not just circular ones)
     366  // ---------------------------------------------------------------------
     367  ColumnVector h = crossproduct(xSat, vInert);
     368  double       hNorm = sqrt(DotProduct(h, h));
     369  ColumnVector orbNormal = h / hNorm;
     370  double       r = sqrt(DotProduct(xSat, xSat));
     371  double       nRate = hNorm / (r * r); // [rad/s]
     372
     373  // Beta angle (Sun elevation above the orbital plane)
     374  // ----------------------------------------------------
     375  double beta = asin(DotProduct(orbNormal, xSun));
     376
     377  // Orbit angle mu, measured from the orbit midnight point, increasing in
     378  // the direction of satellite motion
     379  // -----------------------------------------------------------------------
     380  ColumnVector sunProj = xSun - DotProduct(xSun, orbNormal) * orbNormal;
     381  sunProj /= sqrt(DotProduct(sunProj, sunProj));
     382  ColumnVector eX = -1.0 * sunProj;                 // midnight direction, mu = 0
     383  ColumnVector eY = crossproduct(orbNormal, eX);
     384  ColumnVector rHat = xSat / r;
     385  double mu = atan2(DotProduct(rHat, eY), DotProduct(rHat, eX));
     386
     387  // Nominal Sun-pointing yaw angle and its rate (Bar-Sever 1996)
     388  // ----------------------------------------------------------------
     389  double tanBeta = tan(beta);
     390  double sinMu   = sin(mu);
     391  double psiNom  = atan2(-tanBeta, sinMu);
     392  double denom   = tanBeta * tanBeta + sinMu * sinMu;
     393  double psiRate = (denom > 1e-12)
     394                  ? nRate * fabs(tanBeta * cos(mu)) / denom
     395                  : 1e9;
     396
     397  t_glonassYaw& st = _glonassYaw[prn];
     398  double psiEff;
     399  if (psiRate > MAX_YAW_RATE) {
     400    if (!st.valid) {
     401      st.yaw = psiNom; // no prior history - best available estimate
     402    }
     403    psiEff = st.yaw;    // hold the frozen yaw angle
     404  }
     405  else {
     406    st.yaw   = psiNom;
     407    st.valid = true;
     408    psiEff   = psiNom;
     409  }
     410
     411  return psiEff;
     412}
     413
    332414// Satellite Antenna Offset
    333415////////////////////////////////////////////////////////////////////////////
    334416t_irc bncAntex::satCoMcorrection(const QString& prn, double Mjd,
    335                                  const ColumnVector& xSat, ColumnVector& dx) {
     417                                 const ColumnVector& xSat,
     418                                 const ColumnVector& vSat, ColumnVector& dx) {
    336419
    337420  t_frequency::type frqType = t_frequency::dummy;
     
    374457      xSun /= sqrt(DotProduct(xSun,xSun));
    375458
    376       ColumnVector sy = crossproduct(sz, xSun);
    377       sy /= sqrt(DotProduct(sy,sy));
    378 
    379       ColumnVector sx = crossproduct(sy, sz);
     459      ColumnVector sy, sx;
     460
     461      // GLONASS: override the nominal Sun-pointing attitude near the
     462      // orbit noon/midnight points when the beta angle is small (see
     463      // glonassYawAngle() above). Elsewhere GLONASS-M follows the same
     464      // nominal law as the other constellations, so the result is
     465      // identical to the direct Sun-pointing computation used below.
     466      // -----------------------------------------------------------------
     467      if (prn[0] == 'R' && vSat.size() == 3) {
     468        double psi = glonassYawAngle(prn, xSat, vSat, xSun);
     469
     470        ColumnVector vInert = vSat;
     471        ColumnVector Omega(3); Omega(1) = 0.0; Omega(2) = 0.0; Omega(3) = t_CST::omega;
     472        vInert += crossproduct(Omega, xSat);
     473
     474        ColumnVector sy0 = crossproduct(sz, vInert);
     475        sy0 /= sqrt(DotProduct(sy0,sy0));
     476        ColumnVector sx0 = crossproduct(sy0, sz);
     477
     478        // Rodrigues rotation of (sx0, sy0) around sz by angle psi
     479        double cosY = cos(psi);
     480        double sinY = sin(psi);
     481        sx = sx0 * cosY + crossproduct(sz, sx0) * sinY;
     482        sy = sy0 * cosY + crossproduct(sz, sy0) * sinY;
     483      }
     484      else {
     485        sy = crossproduct(sz, xSun);
     486        sy /= sqrt(DotProduct(sy,sy));
     487        sx = crossproduct(sy, sz);
     488      }
    380489
    381490      dx[0] = sx[0] * neu[0] + sy[0] * neu[1] + sz[0] * neu[2];
Note: See TracChangeset for help on using the changeset viewer.