Changeset 11047 in ntrip for trunk/BNC/src/PPP/pppSatObs.cpp
- Timestamp:
- Sep 28, 2026, 11:17:34 AM (7 hours ago)
- File:
-
- 1 edited
-
trunk/BNC/src/PPP/pppSatObs.cpp (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/BNC/src/PPP/pppSatObs.cpp
r11042 r11047 400 400 401 401 402 // A system's Satellite Antenna message is only trustworthy once the 403 // provider has confirmed it via Metadata: SatelliteAntennaIOD (DF+010) must 404 // be non-zero, and must match the Data IOD (DF+069) of a Metadata entry for 405 // model-correction type 1 (satellite antenna PCV) or type 2 (GDV) - either 406 // entry matching is sufficient. Metadata carries this IOD once globally, not 407 // per system (DF+010 is GNSS-specific, DF+069 is not), so the same Metadata 408 // entries are reused as the trust anchor for every system's check - the 409 // provider is relied on to keep DF+010 synchronized with DF+069 across all 410 // systems for this to work. 402 // Satellite Antenna IOD referenced by the SSR Metadata: the Data IOD (DF+069) 403 // of the model-correction entry of type 1 (satellite antenna PCV) or type 2 404 // (GDV). refIOD = 0 if Metadata references no antenna information (no 405 // Metadata, no such entry, no Data IOD, or a zero Data IOD). A provider's 406 // solution is relative to one Satellite Antenna information only, so two 407 // different non-zero IODs are inconsistent: returns false in that case. 408 // 409 // A non-zero reference means the provider's biases, and orbits/clocks, are 410 // relative to exactly that Satellite Antenna information, so the client 411 // must apply it and nothing else (see t_pppSatObs::cmpModel()). 412 //////////////////////////////////////////////////////////////////////////// 413 static bool ssrSatAntennaRefIOD(const t_metaData* metaData, unsigned int& refIOD) { 414 refIOD = 0; 415 if (!metaData) { 416 return true; 417 } 418 for (unsigned ii = 0; ii < metaData->_entries.size(); ii++) { 419 const t_metaDataEntry& entry = metaData->_entries[ii]; 420 if ((entry._typeIndicator == 1 || entry._typeIndicator == 2) && // PCV or GDV 421 entry._dataIODIndicator && entry._dataIOD != 0) { 422 if (refIOD != 0 && refIOD != entry._dataIOD) { 423 return false; 424 } 425 refIOD = entry._dataIOD; 426 } 427 } 428 return true; 429 } 430 431 // Whether cached Satellite Antenna data is the information referenced by 432 // Metadata (refIOD, from ssrSatAntennaRefIOD()). SatelliteAntennaIOD (DF+010) 433 // must be non-zero and equal refIOD. Metadata carries this IOD once globally, 434 // not per system (DF+010 is GNSS-specific, DF+069 is not), so the same 435 // Metadata entries are reused for every system's check - the provider is 436 // relied on to keep DF+010 synchronized with DF+069 across all systems. 437 // 438 // The IOD is also unique only per SSR Provider ID (DF414), so the Metadata 439 // must come from the same provider as the Antenna message - antenna data may 440 // be taken from the bias mountpoint while Metadata comes from the 441 // corrections mountpoint (see pppRun.cpp). 411 442 // 412 443 // Separately, providers only guarantee SatelliteAntennaIOD uniqueness within … … 414 445 // has been without a fresh Antenna message for that system for 64 days or 415 446 // more must treat any cached data as unverifiable and stop using it, even 416 // if the IOD/Metadata check above would otherwise pass - the same IOD value 417 // could by then legitimately mean something else. epoTime is the current 418 // processing epoch, compared against t_satAntenna::_time (when this data 419 // was actually decoded, not a wire epoch - the message carries none). 420 //////////////////////////////////////////////////////////////////////////// 421 static bool ssrSatAntennaTrusted(const t_satAntenna* satAntenna, const bncTime& epoTime) { 447 // if the IOD check above would otherwise pass - the same IOD value could by 448 // then legitimately mean something else. epoTime is the current processing 449 // epoch, compared against t_satAntenna::_time (when this data was last 450 // received, to within the decoder's re-emit interval - not a wire epoch, 451 // the message carries none; see RTCM3coDecoder::sendResults()). 452 //////////////////////////////////////////////////////////////////////////// 453 static bool ssrSatAntennaTrusted(const t_satAntenna* satAntenna, const t_metaData* metaData, 454 unsigned int refIOD, const bncTime& epoTime) { 422 455 const double MAX_AGE_SEC = 64.0 * 86400.0; 423 if (!satAntenna || satAntenna->_satelliteAntennaIOD == 0) { 456 if (!satAntenna || !metaData || refIOD == 0 || 457 satAntenna->_satelliteAntennaIOD != refIOD || 458 satAntenna->_ssrProviderID != metaData->_providerID) { 424 459 return false; 425 460 } … … 428 463 return false; 429 464 } 430 const t_metaData* metaData = PPP_CLIENT->obsPool()->metaData(); 431 if (!metaData) { 432 return false; 433 } 434 for (unsigned ii = 0; ii < metaData->_entries.size(); ii++) { 435 const t_metaDataEntry& entry = metaData->_entries[ii]; 436 if ((entry._typeIndicator == 1 || entry._typeIndicator == 2) && // PCV or GDV 437 entry._dataIODIndicator && 438 entry._dataIOD == satAntenna->_satelliteAntennaIOD) { 439 return true; 440 } 441 } 442 return false; 465 return true; 443 466 } 444 467 445 468 // Satellite antenna correction (offset + nadir-angle-dependent PCV) from a 446 469 // live SSR Satellite Antenna message, in the same additive sense as 447 // bncAntex::satCorr(). Returns false (corr untouched) if no trusted SSR data 448 // is available for this PRN/frequency (see ssrSatAntennaTrusted), so the 449 // caller falls back to the static ANTEX file. 470 // bncAntex::satCorr(). Returns false (corr untouched) if the data contains 471 // no entry for this frequency. 450 472 //////////////////////////////////////////////////////////////////////////// 451 473 static bool ssrSatAntennaCorr(const t_satAntenna* satAntenna, const string& frqStr, … … 472 494 else if (idx >= (int)frq._nadirAngleCorrection.size()) { 473 495 idx = (int)frq._nadirAngleCorrection.size() - 1; 496 } 497 if (std::isnan(frq._nadirAngleCorrection[idx])) { 498 return false; // invalid value (DF+021 = -2^(n-1)) at this nadir angle 474 499 } 475 500 corr += frq._nadirAngleCorrection[idx]; … … 555 580 // Antenna Phase Center Offsets and Variations 556 581 // ------------------------------------------- 582 // If SSR Metadata references Satellite Antenna information (non-zero IOD), 583 // the provider's biases are relative to exactly that information, so it 584 // is the only satellite antenna model applied - never mixed with ANTEX: 585 // phaseranges get the SSR correction if the Phase Center indicator 586 // (DF+011) is set, otherwise zero; pseudoranges likewise with the Group 587 // Delay indicator (DF+012). Zero values are part of that information 588 // (explicitly encoded, or DF+019/DF+013 not set); omitting a satellite or 589 // frequency within a constellation is not a valid way to signal zero. So 590 // if the referenced information is not (yet) available for this satellite, 591 // or lacks a frequency the PPP linear combinations need, the satellite is 592 // excluded rather than corrected with ANTEX. Without such a reference, 593 // the static ANTEX file is used for both observables. 557 594 if (PPP_CLIENT->antex()) { 595 const t_metaData* metaData = PPP_CLIENT->obsPool()->metaData(); 558 596 const t_satAntenna* satAntenna = PPP_CLIENT->obsPool()->satAntenna(_prn); 559 if (!ssrSatAntennaTrusted(satAntenna, _time)) { 560 satAntenna = 0; // untrusted (IOD zero, or not confirmed via Metadata) - fall back to ANTEX 561 } 597 unsigned int refIOD; 598 if (!ssrSatAntennaRefIOD(metaData, refIOD)) { 599 LOG << "SSR Metadata references different Satellite Antenna IODs (PCV/GDV) - " 600 << _prn.toString() << " excluded" << endl; 601 return failure; 602 } 603 bool useSsr = (refIOD != 0); 604 if (useSsr && !ssrSatAntennaTrusted(satAntenna, metaData, refIOD, _time)) { 605 LOG << "SSR Satellite Antenna IOD " << refIOD << " referenced by Metadata not available for " 606 << _prn.toString() << " - satellite excluded" << endl; 607 return failure; 608 } 609 610 bool phaseMissing[t_frequency::max] = {}; 611 bool codeMissing[t_frequency::max] = {}; 612 562 613 for (unsigned ii = 0; ii < t_frequency::max; ii++) { 563 614 t_frequency::type frqType = static_cast<t_frequency::type>(ii); … … 568 619 _model._antPCO[ii] = PPP_CLIENT->antex()->rcvCorr(station->antName(), frqType, _model._eleSat, _model._azSat, found); 569 620 570 // Prefer a live, trusted SSR Satellite Antenna correction over the 571 // static ANTEX file when available for this satellite/frequency, 572 // mirroring how SSR orbit/clock corrections already take precedence 573 // over broadcast ephemerides elsewhere in this codebase. Unlike 574 // ANTEX's satCorr() (a single geometric PCO/PCV value shared by code 575 // and phase alike), the SSR message's DF+011/DF+012 indicators say 576 // explicitly which observable(s) the correction applies to, so it is 577 // routed into the dedicated phase-only/code-only fields instead of 578 // the shared _antPCO - both may fire for the same ssrCorr value if 579 // both indicators are set. 580 double ssrCorr; 581 if (ssrSatAntennaCorr(satAntenna, frqStr, _model._elTx, ssrCorr)) { 582 if (satAntenna->_phaseCenterInfoInd) { 583 _model._antPCV[ii] += ssrCorr; 584 } 585 if (satAntenna->_groupDelayInfoInd) { 586 _model._antGDV[ii] += ssrCorr; 621 if (useSsr) { 622 // Unlike ANTEX's satCorr() (a single geometric PCO/PCV value shared 623 // by code and phase alike), DF+011/DF+012 say explicitly which 624 // observable(s) the correction applies to, so it is routed into the 625 // dedicated phase-only/code-only fields instead of the shared _antPCO. 626 // BDS 1207.14 MHz: B2 (BDS-2, "C7") or B2b (BDS-3, "C7b") - a 627 // satellite transmits only one of them. The entry of the satellite's 628 // own generation is preferred, taken from the ANTEX block type valid 629 // at this epoch (PRNs have been reassigned from BDS-2 to BDS-3). 630 // Since antenna corrections depend on the carrier, not the signal, 631 // the other entry is used as fallback if the own one is missing; if 632 // the generation is unknown, an entry is used only if unambiguous 633 // (just one present, or both equal). 634 // PRELIMINARY - pending clarification by RTCM SC-104 whether B2 and 635 // B2b entries of one satellite are meant to be identical; to revert 636 // to the strict selection, drop the fallback branches below. 637 double ssrCorr = 0.0; 638 bool ssrOK = false; 639 if (frqStr == "C7") { 640 double corrB2 = 0.0, corrB2b = 0.0; 641 bool hasB2 = ssrSatAntennaCorr(satAntenna, "C7", _model._elTx, corrB2); 642 bool hasB2b = ssrSatAntennaCorr(satAntenna, "C7b", _model._elTx, corrB2b); 643 QString blockType = PPP_CLIENT->antex()->satBlockType(prn, _time); 644 if (blockType.startsWith("3")) { // BDS-3: B2b, fallback B2 645 ssrOK = hasB2b || hasB2; 646 ssrCorr = hasB2b ? corrB2b : corrB2; 647 } 648 else if (blockType.startsWith("2")) { // BDS-2: B2, fallback B2b 649 ssrOK = hasB2 || hasB2b; 650 ssrCorr = hasB2 ? corrB2 : corrB2b; 651 } 652 else if (hasB2 != hasB2b || (hasB2 && corrB2 == corrB2b)) { // generation unknown 653 ssrOK = true; 654 ssrCorr = hasB2 ? corrB2 : corrB2b; 655 } 656 } 657 else { 658 ssrOK = ssrSatAntennaCorr(satAntenna, frqStr, _model._elTx, ssrCorr); 659 } 660 if (ssrOK) { 661 if (satAntenna->_phaseCenterInfoInd) { 662 _model._antPCV[ii] += ssrCorr; 663 } 664 if (satAntenna->_groupDelayInfoInd) { 665 _model._antGDV[ii] += ssrCorr; 666 } 667 } 668 else { 669 phaseMissing[ii] = satAntenna->_phaseCenterInfoInd; 670 codeMissing[ii] = satAntenna->_groupDelayInfoInd; 587 671 } 588 672 } … … 604 688 _model._antPCO[ii] -= PPP_CLIENT->antex()->satCorr(prn, t_frequency::C2, _model._elTx, _model._azTx, found); 605 689 } 690 } 691 } 692 } 693 694 // If the SSR antenna information applies to both observables (both 695 // DF+011 and DF+012 set), the code and phase biases must come from the 696 // same SSR solution (same SSR IOD and Provider ID), otherwise they are 697 // not consistent with each other with respect to it. Only checked if 698 // phase biases are used and both IODs are known. 699 if (useSsr && satAntenna->_phaseCenterInfoInd && satAntenna->_groupDelayInfoInd && 700 OPT->arSystem(_prn.system())) { 701 const t_satCodeBias* satCodeBias = PPP_CLIENT->obsPool()->satCodeBias(_prn); 702 const t_satPhaseBias* satPhaseBias = PPP_CLIENT->obsPool()->satPhaseBias(_prn); 703 if (satCodeBias && satPhaseBias && 704 satCodeBias->_ssrIOD >= 0 && satPhaseBias->_ssrIOD >= 0 && 705 (satCodeBias->_ssrIOD != satPhaseBias->_ssrIOD || 706 satCodeBias->_ssrProviderID != satPhaseBias->_ssrProviderID)) { 707 LOG << "SSR code bias (IOD " << satCodeBias->_ssrIOD << ", provider " << satCodeBias->_ssrProviderID 708 << ") and phase bias (IOD " << satPhaseBias->_ssrIOD << ", provider " << satPhaseBias->_ssrProviderID 709 << ") inconsistent for " << _prn.toString() << " with shared SSR Satellite Antenna information" 710 << " - satellite excluded" << endl; 711 return failure; 712 } 713 } 714 715 // Exclude the satellite if the referenced antenna information omits a 716 // frequency actually used by the linear combinations of this system 717 const vector<t_lc>& LCs = OPT->LCs(_prn.system()); 718 for (unsigned iLC = 0; iLC < LCs.size(); iLC++) { 719 map<t_frequency::type, double> codeCoeff; 720 map<t_frequency::type, double> phaseCoeff; 721 map<t_frequency::type, double> ionoCoeff; 722 lcCoeff(LCs[iLC], codeCoeff, phaseCoeff, ionoCoeff); 723 map<t_frequency::type, double>::const_iterator it; 724 for (it = phaseCoeff.begin(); it != phaseCoeff.end(); it++) { 725 if (phaseMissing[it->first]) { 726 LOG << "SSR Satellite Antenna IOD " << refIOD << " has no valid entry for " 727 << _prn.toString() << ' ' << t_frequency::toString(it->first) << " (phase) - satellite excluded" << endl; 728 return failure; 729 } 730 } 731 for (it = codeCoeff.begin(); it != codeCoeff.end(); it++) { 732 if (codeMissing[it->first]) { 733 LOG << "SSR Satellite Antenna IOD " << refIOD << " has no valid entry for " 734 << _prn.toString() << ' ' << t_frequency::toString(it->first) << " (code) - satellite excluded" << endl; 735 return failure; 606 736 } 607 737 }
Note:
See TracChangeset
for help on using the changeset viewer.
