Allow BeiDou B2a signals in multi-band configurations

This commit is contained in:
Carles Fernandez
2026-09-16 07:47:19 +02:00
parent 8ddffe821d
commit 75ae5ce4e4
14 changed files with 317 additions and 118 deletions
+12
View File
@@ -914,6 +914,18 @@ std::map<int, Beidou_Dnav_Ephemeris> Rtklib_Pvt::get_beidou_dnav_ephemeris() con
}
std::map<int, Beidou_Cnav1_Ephemeris> Rtklib_Pvt::get_beidou_cnav1_ephemeris() const
{
return pvt_->get_beidou_cnav1_ephemeris_map();
}
std::map<int, Beidou_Cnav1_Ephemeris> Rtklib_Pvt::get_beidou_cnav2_ephemeris() const
{
return pvt_->get_beidou_cnav2_ephemeris_map();
}
std::map<int, Gps_Almanac> Rtklib_Pvt::get_gps_almanac() const
{
return pvt_->get_gps_almanac_map();
+2
View File
@@ -231,6 +231,8 @@ public:
std::map<int, Gps_Ephemeris> get_gps_ephemeris() const override;
std::map<int, Galileo_Ephemeris> get_galileo_ephemeris() const override;
std::map<int, Beidou_Dnav_Ephemeris> get_beidou_dnav_ephemeris() const override;
std::map<int, Beidou_Cnav1_Ephemeris> get_beidou_cnav1_ephemeris() const override;
std::map<int, Beidou_Cnav1_Ephemeris> get_beidou_cnav2_ephemeris() const override;
std::map<int, Gps_Almanac> get_gps_almanac() const override;
std::map<int, Galileo_Almanac> get_galileo_almanac() const override;
std::map<int, Beidou_Dnav_Almanac> get_beidou_dnav_almanac() const override;
@@ -198,6 +198,7 @@ struct rtklib_pvt_gs::NavigationSnapshot
using GalileoEphemerisMap = std::map<int, Galileo_Ephemeris>;
using GalileoAlmanacMap = std::map<int, Galileo_Almanac>;
using BeidouEphemerisMap = std::map<int, Beidou_Dnav_Ephemeris>;
using BeidouCnavEphemerisMap = std::map<int, Beidou_Cnav1_Ephemeris>;
using BeidouAlmanacMap = std::map<int, Beidou_Dnav_Almanac>;
std::shared_ptr<const GpsCnavEphemerisMap> gps_cnav_ephemeris = std::make_shared<const GpsCnavEphemerisMap>();
@@ -209,6 +210,8 @@ struct rtklib_pvt_gs::NavigationSnapshot
std::shared_ptr<const GalileoEphemerisMap> galileo_ephemeris = std::make_shared<const GalileoEphemerisMap>();
std::shared_ptr<const GalileoAlmanacMap> galileo_almanac = std::make_shared<const GalileoAlmanacMap>();
std::shared_ptr<const BeidouEphemerisMap> beidou_ephemeris = std::make_shared<const BeidouEphemerisMap>();
std::shared_ptr<const BeidouCnavEphemerisMap> beidou_cnav1_ephemeris = std::make_shared<const BeidouCnavEphemerisMap>();
std::shared_ptr<const BeidouCnavEphemerisMap> beidou_cnav2_ephemeris = std::make_shared<const BeidouCnavEphemerisMap>();
std::shared_ptr<const BeidouAlmanacMap> beidou_almanac = std::make_shared<const BeidouAlmanacMap>();
};
@@ -2104,6 +2107,11 @@ void rtklib_pvt_gs::msg_handler_telemetry(const pmt::pmt_t& msg)
{
publish_navigation_snapshot(NavigationData::BeidouEphemeris);
}
else if (msg_type_hash_code == d_beidou_cnav1_ephemeris_sptr_type_hash_code ||
msg_type_hash_code == d_beidou_cnav1_page_data_sptr_type_hash_code)
{
publish_navigation_snapshot(NavigationData::BeidouCnavEphemeris);
}
else if (msg_type_hash_code == d_beidou_dnav_almanac_sptr_type_hash_code)
{
publish_navigation_snapshot(NavigationData::BeidouAlmanac);
@@ -2232,6 +2240,23 @@ void rtklib_pvt_gs::publish_navigation_snapshot(NavigationData data)
case NavigationData::BeidouEphemeris:
updated->beidou_ephemeris = std::make_shared<const NavigationSnapshot::BeidouEphemerisMap>(d_internal_pvt_solver->beidou_dnav_ephemeris_map);
break;
case NavigationData::BeidouCnavEphemeris:
{
auto cnav1 = std::make_shared<NavigationSnapshot::BeidouCnavEphemerisMap>(d_internal_pvt_solver->beidou_cnav1_ephemeris_map);
// B-CNAV1 health may arrive on a later page without a new orbit.
// Use the same latest-page health as the PVT solver.
for (auto& entry : *cnav1)
{
const auto page = d_internal_pvt_solver->beidou_cnav1_page_data_map.find(entry.first);
if (page != d_internal_pvt_solver->beidou_cnav1_page_data_map.cend())
{
entry.second.hs = page->second.common.hs;
}
}
updated->beidou_cnav1_ephemeris = std::move(cnav1);
updated->beidou_cnav2_ephemeris = std::make_shared<const NavigationSnapshot::BeidouCnavEphemerisMap>(d_internal_pvt_solver->beidou_cnav2_ephemeris_map);
break;
}
case NavigationData::BeidouAlmanac:
updated->beidou_almanac = std::make_shared<const NavigationSnapshot::BeidouAlmanacMap>(d_internal_pvt_solver->beidou_dnav_almanac_map);
break;
@@ -2292,6 +2317,18 @@ std::map<int, Beidou_Dnav_Ephemeris> rtklib_pvt_gs::get_beidou_dnav_ephemeris_ma
}
std::map<int, Beidou_Cnav1_Ephemeris> rtklib_pvt_gs::get_beidou_cnav1_ephemeris_map() const
{
return *navigation_snapshot()->beidou_cnav1_ephemeris;
}
std::map<int, Beidou_Cnav1_Ephemeris> rtklib_pvt_gs::get_beidou_cnav2_ephemeris_map() const
{
return *navigation_snapshot()->beidou_cnav2_ephemeris;
}
std::map<int, Beidou_Dnav_Almanac> rtklib_pvt_gs::get_beidou_dnav_almanac_map() const
{
const auto snapshot = navigation_snapshot();
@@ -2310,6 +2347,9 @@ void clear_navigation_maps(Rtklib_Solver& solver, bool keep_almanac)
solver.galileo_ephemeris_store.clear();
solver.galileo_reduced_ced_map.clear();
solver.beidou_dnav_ephemeris_map.clear();
solver.beidou_cnav1_ephemeris_map.clear();
solver.beidou_cnav2_ephemeris_map.clear();
solver.beidou_cnav1_page_data_map.clear();
if (keep_almanac)
{
return;
@@ -2353,6 +2393,8 @@ void rtklib_pvt_gs::request_navigation_clear(NavigationClear kind)
almanac_only->glonass_ephemeris = d_empty_navigation_snapshot->glonass_ephemeris;
almanac_only->galileo_ephemeris = d_empty_navigation_snapshot->galileo_ephemeris;
almanac_only->beidou_ephemeris = d_empty_navigation_snapshot->beidou_ephemeris;
almanac_only->beidou_cnav1_ephemeris = d_empty_navigation_snapshot->beidou_cnav1_ephemeris;
almanac_only->beidou_cnav2_ephemeris = d_empty_navigation_snapshot->beidou_cnav2_ephemeris;
replacement = std::move(almanac_only);
}
// A full clear requested before the worker has applied an
@@ -57,6 +57,7 @@ class Glonass_Gnav_Almanac;
class Glonass_Gnav_Utc_Model;
class Beidou_Dnav_Almanac;
class Beidou_Dnav_Ephemeris;
class Beidou_Cnav1_Ephemeris;
class Galileo_Almanac;
class Galileo_Ephemeris;
class Galileo_HAS_data;
@@ -123,6 +124,8 @@ public:
* \brief Get latest set of BeiDou DNAV ephemeris from PVT block
*/
std::map<int, Beidou_Dnav_Ephemeris> get_beidou_dnav_ephemeris_map() const;
std::map<int, Beidou_Cnav1_Ephemeris> get_beidou_cnav1_ephemeris_map() const;
std::map<int, Beidou_Cnav1_Ephemeris> get_beidou_cnav2_ephemeris_map() const;
/*!
* \brief Get latest set of BeiDou DNAV almanac from PVT block
@@ -240,6 +243,7 @@ private:
GalileoEphemeris,
GalileoAlmanac,
BeidouEphemeris,
BeidouCnavEphemeris,
BeidouAlmanac,
RetainedAlmanacs
};
+59 -28
View File
@@ -161,14 +161,23 @@ Rtklib_Solver::Rtklib_Solver(const rtk_t &rtk,
d_rtklib_freq_index[0] = 2;
}
// B2a-only SPP uses RTKLIB slot 0. Default "5D" mapping is slot 2
// (L1+L2+L5). RTKLIB BDS satwavelen frq2 is B3 (1268.52 MHz), not L5,
// so lam[0] is overridden to c/FREQ5 in get_PVT. This remap is gated on
// B2a-only: B1I/B1C/B3 present keeps the default dual-frequency map.
if (flags.check_only_enabled(BDS_B2A))
// Resolve BeiDou slots independently of the other constellations. B2a
// owns slot 2 in a frequency pair; B3I uses the remaining slot when a
// B1 signal is enabled, or the primary slot for B3I+B2a reception.
if (flags.check_any_enabled(BDS_B2A))
{
d_rtklib_band_index["5D"] = 0;
d_rtklib_freq_index[0] = 2;
if (flags.check_any_enabled(BDS_B1, BDS_B1C))
{
d_rtklib_band_index["B3"] = 1;
}
else if (flags.check_any_enabled(BDS_B3))
{
d_rtklib_band_index["B3"] = 0;
}
else
{
d_rtklib_band_index["5D"] = 0;
}
}
// In automatic I/NAV mode E5a observations are not admitted to PVT, so
@@ -1542,6 +1551,40 @@ void Rtklib_Solver::reset_relative_filter()
}
void Rtklib_Solver::update_beidou_observation_wavelengths(const obsd_t &observation)
{
if (satsys(observation.sat, nullptr) != SYS_BDS)
{
return;
}
for (int band = 0; band < NFREQ; ++band)
{
const unsigned char code = observation.code[band];
double frequency = 0.0;
if (is_bds_b1c_code(code))
{
frequency = FREQ1;
}
else if (is_bds_b2a_code(code))
{
frequency = FREQ5;
}
else if (code == CODE_L6I || code == CODE_L6Q)
{
frequency = FREQ3_BDS;
}
else if (code == CODE_L2I || code == CODE_L1I)
{
frequency = FREQ1_BDS;
}
if (frequency > 0.0)
{
d_nav_data.lam[observation.sat - 1][band] = SPEED_OF_LIGHT_M_S / frequency;
}
}
}
int Rtklib_Solver::merge_duplicated_rover_observations(int rover_observation_count)
{
/* Two channels can deliver the same satellite on the same signal (e.g. a
@@ -1573,6 +1616,7 @@ int Rtklib_Solver::merge_duplicated_rover_observations(int rover_observation_cou
continue;
}
obsd_t &destination = d_obs_data[match];
bool overlapping_signal = false;
for (int frequency = 0; frequency < NFREQ + NEXOBS; ++frequency)
{
const bool source_has_data = source.code[frequency] != CODE_NONE &&
@@ -1583,6 +1627,7 @@ int Rtklib_Solver::merge_duplicated_rover_observations(int rover_observation_cou
}
const bool destination_has_data = destination.code[frequency] != CODE_NONE &&
(destination.P[frequency] != 0.0 || destination.L[frequency] != 0.0);
overlapping_signal = overlapping_signal || destination_has_data;
if (destination_has_data && destination.SNR[frequency] >= source.SNR[frequency])
{
continue;
@@ -1594,8 +1639,8 @@ int Rtklib_Solver::merge_duplicated_rover_observations(int rover_observation_cou
destination.LLI[frequency] = source.LLI[frequency];
destination.code[frequency] = source.code[frequency];
}
merged_any = true;
if (!d_duplicated_rover_observations_logged)
merged_any = merged_any || overlapping_signal;
if (overlapping_signal && !d_duplicated_rover_observations_logged)
{
LOG(WARNING) << "Duplicated observation of satellite " << satno2id(source.sat)
<< " received from two channels: keeping the measurement with the highest C/N0";
@@ -1946,8 +1991,10 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
d_fixed_base_age_s = 0.0;
d_fixed_base_common_satellites = 0;
d_obs_data.fill({});
std::vector<eph_t> eph_data(MAXOBS);
// Before merging, a multi-band satellite contributes several channel
// records and ephemerides. MAXOBS limits satellites, not input channels.
d_obs_data.assign(std::max(static_cast<size_t>(MAXOBS * 2), gnss_observables_map.size()), obsd_t{});
std::vector<eph_t> eph_data(std::max(static_cast<size_t>(MAXOBS), gnss_observables_map.size()));
std::vector<geph_t> geph_data(MAXOBS);
for (gnss_observables_iter = gnss_observables_map.cbegin();
@@ -2722,25 +2769,9 @@ bool Rtklib_Solver::get_PVT(const std::map<int, Gnss_Synchro> &gnss_observables_
update_galileo_observation_wavelengths(d_obs_data[i]);
}
/* B1C on slot 0: override lam[0] to FREQ1 (satwavelen frq0 is B1I). */
for (int k = 0; k < nobs_total; k++)
{
if (satsys(d_obs_data[k].sat, nullptr) != SYS_BDS)
{
continue;
}
const unsigned char c0 = d_obs_data[k].code[0];
if (is_bds_b1c_code(c0))
{
d_nav_data.lam[d_obs_data[k].sat - 1][0] = SPEED_OF_LIGHT_M_S / FREQ1;
}
for (int band = 0; band < NFREQ; ++band)
{
if (is_bds_b2a_code(d_obs_data[k].code[band]))
{
d_nav_data.lam[d_obs_data[k].sat - 1][band] = SPEED_OF_LIGHT_M_S / FREQ5;
}
}
update_beidou_observation_wavelengths(d_obs_data[k]);
}
const int configured_positioning_mode = d_rtk.opt.mode;
if (use_single_fallback)
+2 -3
View File
@@ -210,8 +210,7 @@ public:
std::map<int, Bds3_B1c_PageData> beidou_cnav1_page_data_map;
private:
friend class GalileoEphemerisSourceTest_E6SlotsFollowRtklibGalileoPolicy_Test;
void update_beidou_observation_wavelengths(const obsd_t& observation);
bool save_matfile() const;
bool prepare_fixed_base_observations(const Ntrip_Rtcm_Snapshot& fixed_base,
int& rover_observation_count,
@@ -246,7 +245,7 @@ private:
void reset_relative_filter();
std::array<obsd_t, MAXOBS * 2> d_obs_data{};
std::vector<obsd_t> d_obs_data = std::vector<obsd_t>(MAXOBS * 2);
// per-epoch scratch of prepare_fixed_base_observations(); members so
// their capacity is reused across epochs
std::vector<obsd_t> d_fixed_base_rover_scratch;
+10 -36
View File
@@ -178,6 +178,10 @@ double gettgd_bds_by_obs_code(int sat, const nav_t *nav, unsigned char obs_code)
int prn = 0;
const int sys = satsys(sat, &prn);
const int is_b1c_obs = is_bds_b1c_code(obs_code) ? 1 : 0;
if (sys == SYS_BDS && (obs_code == CODE_L6I || obs_code == CODE_L6Q))
{
return 0.0; // B3I is the broadcast clock reference.
}
for (i = 0; i < nav->n; i++)
{
@@ -320,46 +324,14 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
}
/* L1-L2 for GPS/GLO/QZS, L1-L5 for GAL/SBS;
* BDS: B1I(band0)+B3I(band2). B1C (CODE_L1D/L1P/L1X) is single-frequency on
* slot 0 under GNSS-SDR prefer-B1C XOR (never paired as IF with B1I). */
* BDS: select from the measured bands, preferring B2a as the IF partner. */
if (sys == SYS_GAL || sys == SYS_SBS)
{
j = 2;
}
else if (sys == SYS_BDS)
{
const bool b1c0 = is_bds_b1c_code(obs->code[0]);
const bool b1c1 = is_bds_b1c_code(obs->code[1]);
const bool b2a0 = is_bds_b2a_code(obs->code[0]);
const bool b2a1 = is_bds_b2a_code(obs->code[1]);
if (b1c0 || b1c1)
{
i = b1c0 ? 0 : 1;
j = i; /* B1C single-frequency */
}
else if (b2a0 || b2a1)
{
i = b2a0 ? 0 : 1;
j = i; /* B2a single-frequency */
}
else if (obs->code[0] != CODE_NONE && obs->code[2] != CODE_NONE)
{
i = 0;
j = 2; /* B1I + B3I */
}
else if (obs->code[0] != CODE_NONE)
{
i = 0;
j = 2; /* B1I-only: keep legacy pair indices for lam check */
}
else if (obs->code[2] != CODE_NONE)
{
/* B3I-only: B3I is the DNAV timing reference, so use it
directly without TGD. rescode() scales the broadcast
ionosphere correction using this observation slot. */
i = 2;
j = 2;
}
bds_observation_slots(obs, &i, &j);
}
else if (sys == SYS_GPS || sys == SYS_GLO || sys == SYS_QZS)
{
@@ -484,9 +456,11 @@ double prange(const obsd_t *obs, const nav_t *nav, const double *azel,
/* iono-free combination */
if (sys == SYS_BDS)
{
/* the DNAV clock is referenced to B3I: remove TGD1 from
the B1I pseudorange before combining (BDS SIS ICD) */
/* DNAV/B-CNAV clocks use B3I as their delay reference.
Correct each signal with its own navigation-message bias
before combining (BDS B2a SIS ICD, section 7.6). */
P1 -= P1_P2;
P2 -= gettgd_bds_by_obs_code(obs->sat, nav, obs->code[j]);
PC = (gamma_ * P1 - P2) / (gamma_ - 1.0);
}
else if ((sys == SYS_GPS || sys == SYS_QZS) && obs->code[j] == CODE_L5X)
+35 -11
View File
@@ -969,7 +969,12 @@ int ifmeas(const obsd_t *obs, const nav_t *nav, const double *azel,
j = 2;
}
if (NFREQ < 2 || lam[i] == 0.0 || lam[j] == 0.0)
if (satsys(obs->sat, nullptr) == SYS_BDS)
{
bds_observation_slots(obs, &i, &j);
}
if (NFREQ < 2 || i == j || lam[i] == 0.0 || lam[j] == 0.0 || lam[i] == lam[j])
{
return 0;
}
@@ -1011,6 +1016,11 @@ int ifmeas(const obsd_t *obs, const nav_t *nav, const double *azel,
{
P2 += P2_C2; /* C2->P2 */
}
if (satsys(obs->sat, nullptr) == SYS_BDS)
{
P1 -= gettgd_bds_by_obs_code(obs->sat, nav, obs->code[i]);
P2 -= gettgd_bds_by_obs_code(obs->sat, nav, obs->code[j]);
}
meas[1] = c1 * P1 + c2 * P2;
var[1] = std::pow(ERR_CBIAS, 2.0);
@@ -1195,17 +1205,24 @@ int corrmeas(const obsd_t *obs, const nav_t *nav, const double *pos,
}
/* L1/L2 geometry-free phase measurement -------------------------------------*/
/* geometry-free phase measurement ------------------------------------------*/
double gfmeas(const obsd_t *obs, const nav_t *nav)
{
const double *lam = nav->lam[obs->sat - 1];
int i = 0;
int j = 1;
if (lam[0] == 0.0 || lam[1] == 0.0 || obs->L[0] == 0.0 || obs->L[1] == 0.0)
if (satsys(obs->sat, nullptr) == SYS_BDS)
{
bds_observation_slots(obs, &i, &j);
}
if (i == j || lam[i] == 0.0 || lam[j] == 0.0 || obs->L[i] == 0.0 || obs->L[j] == 0.0)
{
return 0.0;
}
return lam[0] * obs->L[0] - lam[1] * obs->L[1];
return lam[i] * obs->L[i] - lam[j] * obs->L[j];
}
@@ -1324,7 +1341,8 @@ void detslp_ll(rtk_t *rtk, const obsd_t *obs, int n)
for (i = 0; i < n && i < MAXOBS; i++)
{
for (j = 0; j < rtk->opt.nf; j++)
// Dual-frequency measurements can occupy slots 0 and 2.
for (j = 0; j < NFREQ; j++)
{
if (obs[i].L[j] == 0.0 || !(obs[i].LLI[j] & 3))
{
@@ -1366,7 +1384,7 @@ void detslp_gf(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
trace(3, "detslip_gf: slip detected sat=%2d gf=%8.3f->%8.3f\n",
obs[i].sat, g0, g1);
for (j = 0; j < rtk->opt.nf; j++)
for (j = 0; j < NFREQ; j++)
{
rtk->ssat[obs[i].sat - 1].slip[j] |= 1;
}
@@ -1381,6 +1399,7 @@ void udbias_ppp(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
double meas[2];
double var[2];
std::vector<double> bias(MAXOBS, 0.0);
std::vector<bool> slip(MAXOBS, false);
double offset = 0.0;
double pos[3] = {0};
int i;
@@ -1393,7 +1412,7 @@ void udbias_ppp(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
for (i = 0; i < MAXSAT; i++)
{
for (j = 0; j < rtk->opt.nf; j++)
for (j = 0; j < NFREQ; j++)
{
rtk->ssat[i].slip[j] = 0;
}
@@ -1430,8 +1449,14 @@ void udbias_ppp(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
trace(2, "%s: sat=%2d correction break\n", time_str(obs[i].time, 0), sat);
}
bias[i] = meas[0] - meas[1];
if (rtk->x[j] == 0.0 ||
rtk->ssat[sat - 1].slip[0] || rtk->ssat[sat - 1].slip[1])
int primary = 0;
int secondary = 1;
if (rtk->opt.ionoopt == IONOOPT_IFLC && satsys(sat, nullptr) == SYS_BDS)
{
bds_observation_slots(obs + i, &primary, &secondary);
}
slip[i] = brk || rtk->ssat[sat - 1].slip[primary] || rtk->ssat[sat - 1].slip[secondary];
if (rtk->x[j] == 0.0 || slip[i])
{
continue;
}
@@ -1459,8 +1484,7 @@ void udbias_ppp(rtk_t *rtk, const obsd_t *obs, int n, const nav_t *nav)
rtk->P[j + j * rtk->nx] += std::pow(rtk->opt.prn[0], 2.0) * fabs(rtk->tt);
if (rtk->x[j] != 0.0 &&
!rtk->ssat[sat - 1].slip[0] && !rtk->ssat[sat - 1].slip[1])
if (rtk->x[j] != 0.0 && !slip[i])
{
continue;
}
@@ -670,6 +670,46 @@ bool is_bds_b2a_code(unsigned char code)
}
/* Select the first measured band and prefer B2a as its IF partner. Without
* B2a, retain the B1C single-band and legacy B1I+B3I policies. */
void bds_observation_slots(const obsd_t *obs, int *primary, int *secondary)
{
*primary = 0;
*secondary = 0;
int b2a = -1;
bool found = false;
for (int band = 0; band < NFREQ; ++band)
{
if (obs->code[band] == CODE_NONE || obs->P[band] == 0.0)
{
continue;
}
if (!found)
{
*primary = band;
*secondary = band;
found = true;
}
else
{
*secondary = band;
}
if (is_bds_b2a_code(obs->code[band]))
{
b2a = band;
}
}
if (b2a >= 0 && b2a != *primary)
{
*secondary = b2a;
}
else if (b2a < 0 && is_bds_b1c_code(obs->code[*primary]))
{
*secondary = *primary;
}
}
/* set code priority -----------------------------------------------------------
* set code priority for multiple codes in a frequency
* args : int sys I system (or of SYS_???)
@@ -127,6 +127,7 @@ unsigned char obs2code(const char *obs, int *freq);
char *code2obs(unsigned char code, int *freq);
bool is_bds_b1c_code(unsigned char code);
bool is_bds_b2a_code(unsigned char code);
void bds_observation_slots(const obsd_t *obs, int *primary, int *secondary);
void setcodepri(int sys, int freq, const char *pri);
int getcodepri(int sys, unsigned char code, const char *opt);
unsigned int getbitu(const unsigned char *buff, int pos, int len);
+5
View File
@@ -23,6 +23,7 @@
#ifndef GNSS_SDR_PVT_INTERFACE_H
#define GNSS_SDR_PVT_INTERFACE_H
#include "beidou_cnav1_ephemeris.h"
#include "beidou_dnav_almanac.h"
#include "beidou_dnav_ephemeris.h"
#include "galileo_almanac.h"
@@ -68,6 +69,10 @@ public:
virtual std::map<int, Gps_Ephemeris> get_gps_ephemeris() const = 0;
virtual std::map<int, Galileo_Ephemeris> get_galileo_ephemeris() const = 0;
virtual std::map<int, Beidou_Dnav_Ephemeris> get_beidou_dnav_ephemeris() const = 0;
// Optional navigation families: implementations without B-CNAV support
// retain the existing DNAV/almanac visibility path.
virtual std::map<int, Beidou_Cnav1_Ephemeris> get_beidou_cnav1_ephemeris() const { return {}; }
virtual std::map<int, Beidou_Cnav1_Ephemeris> get_beidou_cnav2_ephemeris() const { return {}; }
virtual std::map<int, Gps_Almanac> get_gps_almanac() const = 0;
virtual std::map<int, Galileo_Almanac> get_galileo_almanac() const = 0;
virtual std::map<int, Beidou_Dnav_Almanac> get_beidou_dnav_almanac() const = 0;
+19 -13
View File
@@ -2499,19 +2499,13 @@ bool GNSSFlowgraph::is_multiband() const
multiband = true;
}
}
if (configuration_->property("Channels_B1.count", 0) > 0)
const bool has_bds_b1 = configuration_->property("Channels_B1.count", 0) > 0 ||
configuration_->property("Channels_1D.count", 0) > 0;
const bool has_bds_b3 = configuration_->property("Channels_B3.count", 0) > 0;
const bool has_bds_b2a = configuration_->property("Channels_5D.count", 0) > 0;
if ((has_bds_b1 && (has_bds_b3 || has_bds_b2a)) || (has_bds_b3 && has_bds_b2a))
{
if (configuration_->property("Channels_B3.count", 0) > 0)
{
multiband = true;
}
}
if (configuration_->property("Channels_1D.count", 0) > 0)
{
if (configuration_->property("Channels_B3.count", 0) > 0)
{
multiband = true;
}
multiband = true;
}
if (configuration_->property("Channels_J1.count", 0) > 0)
{
@@ -2572,12 +2566,15 @@ Gnss_Signal GNSSFlowgraph::search_next_signal(const std::string& searched_signal
assist_signal_candidates = {"B1", "1D"};
break;
case evBDS_B2A:
assist_signal_candidates = {"B1", "1D", "B3"};
break;
case evGPS_1C:
case evGAL_1B:
case evGLO_1G:
case evBDS_B1:
case evBDS_B1C:
case evBDS_B2A:
case evQZS_J1:
case evSBAS_1C:
is_primary_frequency = true;
@@ -2594,6 +2591,15 @@ Gnss_Signal GNSSFlowgraph::search_next_signal(const std::string& searched_signal
const bool any_assist_configured = std::any_of(assist_signal_candidates.begin(), assist_signal_candidates.end(),
[&](const std::string& assist_signal) { return configuration_->property("Channels_" + assist_signal + ".count", 0) > 0; });
// A BeiDou band without an enabled assisting band must acquire on its
// own, even when another constellation makes the receiver multiband.
// In B3I+B2a, B3I starts first and then assists B2a.
if (!any_assist_configured && (searched_signal == "5D" || searched_signal == "B3"))
{
assist_signal_candidates.clear();
is_primary_frequency = true;
}
if (any_assist_configured)
{
// 1. Get the current channel tracking map: locked channels, with or without a
+78 -23
View File
@@ -15,6 +15,7 @@
*/
#include "satellite_visibility.h"
#include "Beidou_CNAV2.h"
#include "GLONASS_L1_L2_CA.h" // for GLONASS_PRN
#include "agnss_ref_location.h" // for parse_agnss_ref_location
#include "agnss_ref_time.h" // for parse_agnss_ref_utc_time
@@ -116,7 +117,7 @@ SatelliteVisibility::SatelliteVisibility(const std::shared_ptr<ConfigurationInte
static const std::map<std::string, std::vector<std::string>> system_signals{
{"GPS", {"1C", "2S", "L5"}},
{"Galileo", {"1B", "5X", "E6", "7X"}},
{"Beidou", {"B1", "1D", "B3"}},
{"Beidou", {"B1", "1D", "B3", "5D"}},
{"Glonass", {"1G", "2G"}},
{"QZSS", {"J1", "J5"}}};
for (const auto& entry : system_signals)
@@ -295,42 +296,82 @@ std::vector<std::pair<int, Gnss_Satellite>> compute_visible_satellites(
}
}
const std::map<int, Beidou_Dnav_Ephemeris> bds_eph_map = pvt_ptr->get_beidou_dnav_ephemeris();
// Classify each BeiDou satellite once, from the usable orbit closest to
// the query epoch. Equal-age records retain DNAV, then CNAV1, then CNAV2
// priority, independent of telemetry arrival order. Almanac remains the
// fallback when no fresh ephemeris is available.
std::map<uint32_t, eph_t> bds_eph_map;
const auto add_bds_ephemeris = [&](uint32_t prn, const eph_t& ephemeris) {
if (only_prns != nullptr && only_prns->count(std::make_pair(std::string("Beidou"), prn)) == 0)
{
return;
}
const double age = timediff(gps_gtime, ephemeris.toe);
if (!std::isfinite(age) || std::abs(age) > MAXDTOE_BDS ||
!std::isfinite(ephemeris.A) || ephemeris.A <= 0.0 ||
!std::isfinite(ephemeris.e) || ephemeris.e < 0.0 || ephemeris.e >= 1.0)
{
return;
}
const auto previous = bds_eph_map.find(prn);
if (previous == bds_eph_map.cend() ||
std::abs(age) < std::abs(timediff(gps_gtime, previous->second.toe)))
{
bds_eph_map[prn] = ephemeris;
}
};
for (const auto& entry : pvt_ptr->get_beidou_dnav_ephemeris())
{
add_bds_ephemeris(entry.second.PRN, eph_to_rtklib(entry.second));
}
const std::array<std::map<int, Beidou_Cnav1_Ephemeris>, 2> bds_cnav_maps{
{pvt_ptr->get_beidou_cnav1_ephemeris(), pvt_ptr->get_beidou_cnav2_ephemeris()}};
for (size_t source = 0; source < bds_cnav_maps.size(); ++source)
{
const int expected_source = source == 0 ? BDS_EPH_SOURCE_CNAV1 : BDS_EPH_SOURCE_CNAV2;
for (const auto& entry : bds_cnav_maps[source])
{
const auto& ephemeris = entry.second;
if (ephemeris.sig_type == expected_source &&
(ephemeris.sat_type == 2 || ephemeris.sat_type == 3))
{
add_bds_ephemeris(ephemeris.PRN, eph_to_rtklib(ephemeris));
}
}
}
for (const auto& it : bds_eph_map)
{
if (only_prns != nullptr && only_prns->count(std::make_pair(std::string("Beidou"), it.second.PRN)) == 0)
{
continue;
}
const eph_t rtklib_eph = eph_to_rtklib(it.second);
const double age = timediff(gps_gtime, rtklib_eph.toe);
if (std::abs(age) > MAXDTOE_BDS)
{
continue; // stale -- treat as if no ephemeris exists; almanac may still classify it
}
note_freshness(age, MAXDTOE_BDS);
const eph_t& rtklib_eph = it.second;
std::array<double, 3> r_sat{};
double clock_bias_s;
double sat_pos_variance_m2;
eph2pos(gps_gtime, &rtklib_eph, r_sat.data(), &clock_bias_s,
&sat_pos_variance_m2);
if (!std::all_of(r_sat.cbegin(), r_sat.cend(), [](double value) { return std::isfinite(value); }))
{
continue;
}
double Az;
double El;
double dist_m;
const arma::vec r_sat_eb_e = arma::vec{r_sat[0], r_sat[1], r_sat[2]};
const arma::vec dx = r_sat_eb_e - r_eb_e;
topocent(&Az, &El, &dist_m, r_eb_e, dx);
handled_bds.push_back(it.second.PRN);
if (El > elevation_mask_deg && it.second.SV_health == 0)
if (!std::isfinite(El))
{
LOG(INFO) << "Using BeiDou Ephemeris: Sat " << it.second.PRN << " Az: " << Az << " El: " << El;
available_satellites.emplace_back(floor(El),
(Gnss_Satellite(std::string("Beidou"), it.second.PRN)));
continue;
}
note_freshness(timediff(gps_gtime, rtklib_eph.toe), MAXDTOE_BDS);
handled_bds.push_back(it.first);
if (El > elevation_mask_deg && rtklib_eph.svh == 0)
{
LOG(INFO) << "Using BeiDou Ephemeris (source " << rtklib_eph.code << "): Sat " << it.first << " Az: " << Az << " El: " << El;
available_satellites.emplace_back(floor(El), Gnss_Satellite(std::string("Beidou"), it.first));
}
else if (below_mask_out != nullptr)
{
LOG(INFO) << "Using BeiDou Ephemeris (excluded): Sat " << it.second.PRN << " Az: " << Az << " El: " << El;
below_mask_out->emplace_back(floor(El), Gnss_Satellite(std::string("Beidou"), it.second.PRN));
LOG(INFO) << "Using BeiDou Ephemeris (source " << rtklib_eph.code << ", excluded): Sat " << it.first << " Az: " << Az << " El: " << El;
below_mask_out->emplace_back(floor(El), Gnss_Satellite(std::string("Beidou"), it.first));
}
}
@@ -566,9 +607,11 @@ bool SatelliteVisibility::DataChanged(const std::shared_ptr<PvtInterface>& pvt_p
{
// Fingerprint on (toe/toa, health) so a replaced entry counts as a
// change, not only a new PRN.
std::map<std::tuple<std::string, std::string, uint32_t>, std::pair<double, int32_t>> current;
auto add = [&current](const char* system, const char* type, uint32_t prn, double ref_time, int32_t health) {
current.emplace(std::make_tuple(std::string(system), std::string(type), prn), std::make_pair(ref_time, health));
std::map<std::tuple<std::string, std::string, uint32_t>, NavigationFingerprint> current;
auto add = [&current](const char* system, const char* type, uint32_t prn, double ref_time, int32_t health,
uint32_t iode = 0, uint32_t iodc = 0, int32_t sat_type = 0, int32_t signal_type = 0) {
current.emplace(std::make_tuple(std::string(system), std::string(type), prn),
std::make_tuple(ref_time, health, iode, iodc, sat_type, signal_type));
};
for (const auto& it : pvt_ptr->get_gps_ephemeris())
{
@@ -582,6 +625,18 @@ bool SatelliteVisibility::DataChanged(const std::shared_ptr<PvtInterface>& pvt_p
{
add("Beidou", "EPH", it.second.PRN, it.second.WN * 604800.0 + it.second.toe, it.second.SV_health);
}
const std::array<std::map<int, Beidou_Cnav1_Ephemeris>, 2> bds_cnav_maps{
{pvt_ptr->get_beidou_cnav1_ephemeris(), pvt_ptr->get_beidou_cnav2_ephemeris()}};
for (size_t source = 0; source < bds_cnav_maps.size(); ++source)
{
for (const auto& it : bds_cnav_maps[source])
{
const auto& ephemeris = it.second;
add("Beidou", source == 0 ? "CNAV1" : "CNAV2", ephemeris.PRN,
ephemeris.WN * 604800.0 + ephemeris.toe, ephemeris.hs,
ephemeris.IODE, ephemeris.IODC, ephemeris.sat_type, ephemeris.sig_type);
}
}
for (const auto& it : pvt_ptr->get_gps_almanac())
{
add(it.second.PRN >= MINPRNQZS && it.second.PRN <= MAXPRNQZS ? "QZSS" : "GPS", "ALM", it.second.PRN, it.second.WNa * 604800.0 + it.second.toa, it.second.SV_health);
+8 -4
View File
@@ -46,7 +46,9 @@ class PvtInterface;
* almanac data in pvt_ptr (GPS, Galileo, BeiDou, GLONASS, QZSS) and returns
* those strictly above elevation_mask_deg that broadcast a healthy status.
* Ephemeris is preferred over almanac, so each satellite is classified by
* exactly one data source. An unhealthy satellite (SV_health != 0; Galileo
* exactly one data source. BeiDou uses the fresh DNAV/CNAV1/CNAV2 orbit
* closest to the query epoch (ties: DNAV, CNAV1, CNAV2); B-CNAV is limited
* to IGSO/MEO satellite types. An unhealthy satellite (SV_health != 0; Galileo
* E1B_HS != 0, since 1B initiates the search) is reported as below-mask, not
* as "no data": it is known unusable whatever its geometry.
*
@@ -230,10 +232,12 @@ private:
double last_fix_time_s_{-1.0};
double last_fix_receiver_time_s_{0.0};
// (system, "EPH"/"ALM"/"CNAV", PRN) -> (toe/toa as absolute seconds,
// health). Catches a PRN's data being replaced, not only new PRNs.
// (system, navigation family, PRN) -> (absolute toe/toa, health,
// IODE, IODC, satellite type, signal type). Extra fields are zero for
// legacy navigation families. Detect replacements as well as new PRNs.
// first_data_check_ tells "never checked" from "checked, still empty".
std::map<std::tuple<std::string, std::string, uint32_t>, std::pair<double, int32_t>> last_data_fingerprints_;
using NavigationFingerprint = std::tuple<double, int32_t, uint32_t, uint32_t, int32_t, int32_t>;
std::map<std::tuple<std::string, std::string, uint32_t>, NavigationFingerprint> last_data_fingerprints_;
// Absolute GPST seconds (gtime_t.time + sec) of the last full sweep:
// monotonic across week rollover, and receiver time keeps the interval