Changeset 11110 in ntrip for trunk/BNC/src/bncantex.cpp
- Timestamp:
- Oct 22, 2026, 11:10:34 AM (32 hours ago)
- File:
-
- 1 edited
-
trunk/BNC/src/bncantex.cpp (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/src/bncantex.cpp
r11104 r11110 385 385 } 386 386 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). 404 397 //////////////////////////////////////////////////////////////////////////// 405 398 double bncAntex::glonassYawAngle(const QString& prn, double Mjd, … … 411 404 412 405 // 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 415 407 // call for longer than this, something else interrupted normal epoch-by- 416 408 // epoch processing (a stream outage, a maneuver-flagged-unhealthy 417 // period, ...), so the stored frozen valueis more likely stale than a418 // still-valid freeze, and we fall back to the current nominal value409 // period, ...), so the stored yaw is more likely stale than a 410 // still-valid turn, and we fall back to the current nominal value 419 411 // instead of trusting it indefinitely. 420 412 const double MAX_CALL_GAP = 1800.0 / 86400.0; // 30 minutes, in days … … 460 452 : 1e9; 461 453 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 462 465 t_glonassYaw& st = _glonassYaw[prn]; 463 466 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 464 478 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 477 483 if (!st.fixed) { 484 st.yaw = st.valid ? st.yaw : psiNom; 485 st.turnSign = psiDotNomSign; 486 st.fixed = true; 478 487 _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", 480 509 prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI, 481 510 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; 485 522 } 486 523 else { 524 // Nominal tracking 487 525 if (st.fixed) { 488 526 _glonassYawLog += QString().asprintf( 489 "%s glonassYaw FIXED-EXITMjd=%.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", 491 529 prn.toLatin1().data(), Mjd, beta*180.0/M_PI, mu*180.0/M_PI, 492 530 psiRate*180.0/M_PI, st.yaw*180.0/M_PI, psiNom*180.0/M_PI); 493 531 st.fixed = false; 494 532 } 495 st.yaw = psiNom;496 533 psiEff = psiNom; 497 534 } 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 499 540 st.valid = true; 500 541 … … 607 648 psiNom*180.0/M_PI, psiEff*180.0/M_PI, 608 649 psiDotNomAbs*psiDotNomSign*180.0/M_PI, psiDotMax*180.0/M_PI); 609 st.inTurn = true; 650 st.inTurn = true; 651 st.turnSign = psiDotNomSign; 610 652 } else { 611 653 // Continuing the turn: integrate at constrained rate 612 654 double dt = callGap * 86400.0; // [s] 613 psiEff = st.yaw + psiDotNomSign * psiDotMax * dt;655 psiEff = st.yaw + st.turnSign * psiDotMax * dt; 614 656 } 615 657 // 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; 616 667 while (psiEff > M_PI) psiEff -= 2.0 * M_PI; 617 668 while (psiEff < -M_PI) psiEff += 2.0 * M_PI; … … 628 679 st.inTurn = false; 629 680 } 630 st.yaw = psiNom;631 681 } 632 682 … … 837 887 // ATT_NOMINAL: simple nominal Sun-pointing for all systems. 838 888 // ATT_COMPUTED or ATT_EXTERNAL: use per-system attitude models. 839 // GLONASS → yaw-fixedmodel (Dilssner et al. 2011)889 // GLONASS → noon/shadow turn model (Dilssner et al. 2011) 840 890 // GPS → noon/midnight turn model (Kouba 2009/2015) 841 891 // others → simple nominal Sun-pointing
Note:
See TracChangeset
for help on using the changeset viewer.
