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


Ignore:
Timestamp:
Oct 22, 2026, 11:10:34 AM (32 hours ago)
Author:
stuerze
Message:

clock combination: some updates regarding attitude correction

File:
1 edited

Legend:

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

    r11104 r11110  
    385385}
    386386
    387 // GLONASS Yaw Angle (Sun-pointing law overridden near noon/midnight when
    388 // the satellite cannot mechanically keep up, following the GLONASS-M
    389 // "yaw-fixed" behaviour described in Dilssner, Springer, Flohrer, Dow
    390 // (2011), "The GLONASS-M satellite yaw-attitude model", Advances in Space
    391 // Research 47(1), 160-171.
    392 //
    393 // Unlike GPS/Galileo/BeiDou, which are commonly approximated by the
    394 // nominal Sun-pointing yaw-steering law of Bar-Sever (1996) at all times,
    395 // GLONASS-M has been found to stop tracking that law and hold a constant
    396 // (frozen) yaw angle whenever the required yaw rate would exceed the
    397 // satellite's slew capability - which happens close to the orbit
    398 // noon/midnight points whenever the Sun's elevation above the orbital
    399 // plane (the "beta" angle) is small. The maximum yaw rate used below
    400 // (0.25 deg/s) and the general approach follow that paper; satellite
    401 // telemetry was not available to validate the exact rate against this
    402 // installation's GLONASS satellites, so it should be checked against
    403 // independently-known attitude/orbit residuals if high accuracy matters.
     387// GLONASS Yaw Angle following the GLONASS-M yaw-attitude model of Dilssner,
     388// Springer, Gienger, Dow (2011), "The GLONASS-M satellite yaw-attitude
     389// model", Advances in Space Research 47(1), 160-171:
     390//  - noon turn: when the nominal yaw rate exceeds the maximum hardware rate
     391//    (0.25 deg/s), the satellite yaws at that rate in the direction of the
     392//    nominal yaw rate until it has caught up with the nominal yaw;
     393//  - midnight turn: in the Earth's shadow the satellite yaws at the maximum
     394//    rate until it reaches the nominal yaw at shadow exit and holds it until
     395//    leaving the shadow.
     396// The maximum rate is applied to all GLONASS satellites (K/K2 like M).
    404397////////////////////////////////////////////////////////////////////////////
    405398double bncAntex::glonassYawAngle(const QString& prn, double Mjd,
    … …  
    411404
    412405  // This bounds the gap BETWEEN CONSECUTIVE CALLS for a given satellite
    413   // (not how long the freeze itself has lasted - a real low-beta passage
    414   // can legitimately stay frozen far longer than this). If there was no
     406  // (not how long a turn lasts). If there was no
    415407  // call for longer than this, something else interrupted normal epoch-by-
    416408  // epoch processing (a stream outage, a maneuver-flagged-unhealthy
    417   // period, ...), so the stored frozen value is more likely stale than a
    418   // still-valid freeze, and we fall back to the current nominal value
     409  // period, ...), so the stored yaw is more likely stale than a
     410  // still-valid turn, and we fall back to the current nominal value
    419411  // instead of trusting it indefinitely.
    420412  const double MAX_CALL_GAP = 1800.0 / 86400.0; // 30 minutes, in days
    … …  
    460452                  : 1e9;
    461453
     454  // Sign of the nominal yaw rate: dPsi/dt = nRate * tanBeta * cos(mu) / denom
     455  double psiDotNomSign = (tanBeta * cos(mu) >= 0.0) ? 1.0 : -1.0;
     456
     457  // Earth's shadow (cylinder): around midnight for |mu| < muShadow
     458  // ----------------------------------------------------------------
     459  const double RE = 6378137.0;
     460  double cosMuShadow = sqrt(std::max(0.0, 1.0 - RE * RE / (r * r))) / cos(beta);
     461  bool   inShadow    = (cosMuShadow < 1.0 && cos(mu) > cosMuShadow);
     462  double muExit      = (cosMuShadow < 1.0) ? acos(cosMuShadow) : 0.0;
     463  double psiExit     = atan2(-tanBeta, sin(muExit)); // nominal yaw at shadow exit
     464
    462465  t_glonassYaw& st = _glonassYaw[prn];
    463466  double callGap = st.valid ? (Mjd - st.lastCallMjd) : 0.0;
     467  if (st.valid && callGap > MAX_CALL_GAP) {
     468    _glonassYawLog += QString().asprintf(
     469      "%s glonassYaw STALE-RESET  Mjd=%.6f beta=%6.3f mu=%7.2f rate=%6.3f"
     470      " old=%7.2f new=%7.2f gap=%.1fmin\n",
     471      prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI,
     472      psiRate*180.0/M_PI, st.yaw*180.0/M_PI, psiNom*180.0/M_PI,
     473      callGap*1440.0);
     474    st.valid = false;
     475    st.fixed = false;
     476  }
     477  double step = MAX_YAW_RATE * callGap * 86400.0;  // max. yaw change since the last call
    464478  double psiEff;
    465   if (psiRate > MAX_YAW_RATE) {
    466     if (!st.valid || callGap > MAX_CALL_GAP) {
    467       if (st.valid) { // i.e. it was a genuine gap, not cold-start
    468         _glonassYawLog += QString().asprintf(
    469           "%s glonassYaw STALE-RESET  Mjd=%.6f beta=%6.3f mu=%7.2f rate=%6.3f"
    470           " old=%7.2f new=%7.2f gap=%.1fmin\n",
    471           prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI,
    472           psiRate*180.0/M_PI, st.yaw*180.0/M_PI, psiNom*180.0/M_PI,
    473           callGap*1440.0);
    474       }
    475       st.yaw = psiNom; // no prior history, or the gap is too long to trust it
    476     }
     479
     480  if (inShadow) {
     481    // Midnight turn: in the shadow the satellite yaws at the maximum rate
     482    // until it reaches the nominal yaw at shadow exit and holds it there
    477483    if (!st.fixed) {
     484      st.yaw      = st.valid ? st.yaw : psiNom;
     485      st.turnSign = psiDotNomSign;
     486      st.fixed    = true;
    478487      _glonassYawLog += QString().asprintf(
    479         "%s glonassYaw FIXED-ENTER  Mjd=%.6f beta=%6.3f mu=%7.2f rate=%6.3f yaw=%7.2f\n",
     488        "%s glonassYaw SHADOW-ENTER Mjd=%.6f beta=%6.3f mu=%7.2f yaw=%7.2f exit=%7.2f\n",
     489        prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI,
     490        st.yaw*180.0/M_PI, psiExit*180.0/M_PI);
     491      psiEff = st.yaw;
     492    }
     493    else if (remainder(st.turnSign * (psiExit - st.yaw), 2.0 * M_PI) > step) {
     494      psiEff = st.yaw + st.turnSign * step;
     495    }
     496    else {
     497      psiEff = psiExit;
     498    }
     499  }
     500  else if (psiRate > MAX_YAW_RATE) {
     501    // Noon turn (or shadow-free midnight turn): the satellite yaws at the
     502    // maximum rate in the direction of the nominal yaw rate
     503    if (!st.valid || !st.fixed) {
     504      st.yaw      = st.valid ? st.yaw : psiNom;
     505      st.turnSign = psiDotNomSign;
     506      st.fixed    = true;
     507      _glonassYawLog += QString().asprintf(
     508        "%s glonassYaw TURN-ENTER   Mjd=%.6f beta=%6.3f mu=%7.2f rate=%6.3f yaw=%7.2f\n",
    480509        prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI,
    481510        psiRate*180.0/M_PI, st.yaw*180.0/M_PI);
    482       st.fixed = true;
    483     }
    484     psiEff = st.yaw;    // hold the frozen yaw angle
     511      psiEff = st.yaw;
     512    }
     513    else {
     514      psiEff = st.yaw + st.turnSign * step;
     515    }
     516  }
     517  else if (st.valid && st.fixed &&
     518           remainder(st.turnSign * (psiNom - st.yaw), 2.0 * M_PI) > step) {
     519    // after the turn or shadow exit, the satellite still lags behind and
     520    // keeps yawing at the maximum rate until it has caught up
     521    psiEff = st.yaw + st.turnSign * step;
    485522  }
    486523  else {
     524    // Nominal tracking
    487525    if (st.fixed) {
    488526      _glonassYawLog += QString().asprintf(
    489         "%s glonassYaw FIXED-EXIT   Mjd=%.6f beta=%6.3f mu=%7.2f rate=%6.3f"
    490         " frozen=%7.2f nominal=%7.2f\n",
     527        "%s glonassYaw TURN-EXIT    Mjd=%.6f beta=%6.3f mu=%7.2f rate=%6.3f"
     528        " yaw=%7.2f nominal=%7.2f\n",
    491529        prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI,
    492530        psiRate*180.0/M_PI, st.yaw*180.0/M_PI, psiNom*180.0/M_PI);
    493531      st.fixed = false;
    494532    }
    495     st.yaw = psiNom;
    496533    psiEff = psiNom;
    497534  }
    498   st.lastCallMjd = Mjd; // update on every call, fixed or not, to detect gaps
     535  while (psiEff >  M_PI) psiEff -= 2.0 * M_PI;
     536  while (psiEff < -M_PI) psiEff += 2.0 * M_PI;
     537
     538  st.yaw         = psiEff;
     539  st.lastCallMjd = Mjd; // update on every call to detect gaps
    499540  st.valid       = true;
    500541
    … …  
    607648        psiNom*180.0/M_PI, psiEff*180.0/M_PI,
    608649        psiDotNomAbs*psiDotNomSign*180.0/M_PI, psiDotMax*180.0/M_PI);
    609       st.inTurn = true;
     650      st.inTurn   = true;
     651      st.turnSign = psiDotNomSign;
    610652    } else {
    611653      // Continuing the turn: integrate at constrained rate
    612654      double dt  = callGap * 86400.0; // [s]
    613       psiEff = st.yaw + psiDotNomSign * psiDotMax * dt;
     655      psiEff = st.yaw + st.turnSign * psiDotMax * dt;
    614656    }
    615657    // Wrap to [-pi, pi]
     658    while (psiEff >  M_PI) psiEff -= 2.0 * M_PI;
     659    while (psiEff < -M_PI) psiEff += 2.0 * M_PI;
     660  }
     661  else if (st.valid && st.inTurn &&
     662           remainder(st.turnSign * (psiNom - st.yaw), 2.0 * M_PI) > psiDotMax * callGap * 86400.0) {
     663    // End of the turn: the nominal yaw rate is below the maximum again, but
     664    // the satellite still lags behind and keeps yawing at the maximum rate
     665    // until it has caught up with the nominal yaw (Kouba 2009)
     666    psiEff = st.yaw + st.turnSign * psiDotMax * callGap * 86400.0;
    616667    while (psiEff >  M_PI) psiEff -= 2.0 * M_PI;
    617668    while (psiEff < -M_PI) psiEff += 2.0 * M_PI;
    … …  
    628679      st.inTurn = false;
    629680    }
    630     st.yaw = psiNom;
    631681  }
    632682
    … …  
    837887      // ATT_NOMINAL: simple nominal Sun-pointing for all systems.
    838888      // ATT_COMPUTED or ATT_EXTERNAL: use per-system attitude models.
    839       //   GLONASS  → yaw-fixed model (Dilssner et al. 2011)
     889      //   GLONASS  → noon/shadow turn model (Dilssner et al. 2011)
    840890      //   GPS      → noon/midnight turn model (Kouba 2009/2015)
    841891      //   others   → simple nominal Sun-pointing
Note: See TracChangeset for help on using the changeset viewer.