Merge PR #1106 (Doppler prediction as acquisition search center) on top of PR #1097

This commit is contained in:
Carles Fernandez
2026-09-23 10:24:28 +00:00
3 changed files with 157 additions and 2 deletions
+28 -2
View File
@@ -63,6 +63,7 @@
#include <iterator> // for insert_iterator, inserter
#include <map> // for std::map
#include <memory> // for std::shared_ptr
#include <optional> // for std::optional
#include <set> // for set
#include <sstream> // for std::stringstream
#include <stdexcept> // for invalid_argument
@@ -1832,8 +1833,33 @@ void GNSSFlowgraph::acquisition_manager(unsigned int who)
}
else
{
// set Doppler center to 0 Hz and search the full Doppler range
channels_[current_channel]->assist_acquisition_doppler(0, 0);
// No dual-frequency assist: fall back to a geometric Doppler
// prediction from ephemeris/almanac (SatelliteVisibility), if
// this is the primary signal of a satellite already classified
// visible and a live PVT fix currently exists. No-op (stays
// nullopt) unless visibility-aware search is enabled --
// PredictedDopplerHz() itself requires a live fix for now; the
// AGNSS-reference (no-fix) case is a follow-up.
std::optional<double> predicted_doppler_hz;
if (is_primary_freq && satellite_visibility_ && satellite_visibility_->enabled())
{
const auto pvt_ptr = get_pvt();
const Gnss_Satellite& sat = channels_[current_channel]->get_signal().get_satellite();
if (pvt_ptr && satellite_visibility_->IsVisible(sat))
{
const Monitor_Pvt fix_status = channels_status_->get_current_status_pvt();
predicted_doppler_hz = satellite_visibility_->PredictedDopplerHz(pvt_ptr, fix_status, sat, channels_[current_channel]->get_signal().get_signal_str());
}
}
if (predicted_doppler_hz.has_value())
{
channels_[current_channel]->assist_acquisition_doppler(*predicted_doppler_hz, 1);
}
else
{
// set Doppler center to 0 Hz and search the full Doppler range
channels_[current_channel]->assist_acquisition_doppler(0, 0);
}
}
#if ENABLE_FPGA
if (enable_fpga_offloading_)
+112
View File
@@ -21,6 +21,7 @@
#include "agnss_ref_time.h" // for parse_agnss_ref_utc_time
#include "configuration_interface.h"
#include "geofunctions.h"
#include "gnss_frequencies.h" // for SIGNAL_FREQ_MAP
#include "monitor_pvt.h"
#include "pvt_interface.h"
#include "qzss.h"
@@ -1056,3 +1057,114 @@ bool SatelliteVisibility::IsSearchExcluded(const Gnss_Satellite& sat) const
{
return GetSearchVisibility(sat) == SearchVisibility::Excluded;
}
std::optional<double> SatelliteVisibility::PredictedDopplerHz(const std::shared_ptr<PvtInterface>& pvt_ptr,
const Monitor_Pvt& fix_status, const Gnss_Satellite& sat, const std::string& signal) const
{
if (!enabled_ || !pvt_ptr || fix_status.RX_time < 0.0)
{
return std::nullopt;
}
// Gnss_Ephemeris::predicted_doppler()/Gnss_Almanac::predicted_doppler()'s
// own band codes, keyed by signal the same way SIGNAL_FREQ_MAP is.
static const std::map<std::string, int> kBandForSignal = {
{"1C", 1}, {"1B", 1}, {"1D", 1}, {"J1", 1}, {"B1", 1},
{"2S", 2}, {"B2", 2},
{"L5", 5}, {"5X", 5}, {"J5", 5},
{"E6", 6},
{"7X", 7},
{"B3", 3}};
const auto band_it = kBandForSignal.find(signal);
const auto freq_it = SIGNAL_FREQ_MAP.find(signal);
if (band_it == kBandForSignal.cend() || freq_it == SIGNAL_FREQ_MAP.cend())
{
return std::nullopt;
}
const int band = band_it->second;
const double carrier_freq_hz = freq_it->second;
const double lat_deg = fix_status.latitude;
const double lon_deg = fix_status.longitude;
const double h_m = fix_status.height;
const double ve_mps = fix_status.vel_e;
const double vn_mps = fix_status.vel_n;
const double vu_mps = fix_status.vel_u;
const gtime_t gps_gtime = gpst2time(static_cast<int>(fix_status.week), fix_status.RX_time);
const double rx_time_s = time2gpst(gps_gtime, nullptr);
int ref_gps_week = 0;
time2gpst(gps_gtime, &ref_gps_week);
const std::string system = sat.get_system();
const auto prn = static_cast<int>(sat.get_PRN());
std::optional<double> geometric_doppler_hz;
if (system == "GPS")
{
const auto eph_map = pvt_ptr->get_gps_ephemeris();
const auto eph_it = eph_map.find(prn);
if (eph_it != eph_map.cend() && std::abs(timediff(gps_gtime, eph_to_rtklib(eph_it->second, ref_gps_week).toe)) <= MAXDTOE)
{
geometric_doppler_hz = eph_it->second.predicted_doppler(rx_time_s, lat_deg, lon_deg, h_m, ve_mps, vn_mps, vu_mps, band);
}
else
{
const auto alm_map = pvt_ptr->get_gps_almanac();
const auto alm_it = alm_map.find(prn);
if (alm_it != alm_map.cend() && std::abs(timediff(gps_gtime, alm_to_rtklib(alm_it->second, ref_gps_week).toa)) <= almanac_max_age_s_)
{
geometric_doppler_hz = alm_it->second.predicted_doppler(rx_time_s, lat_deg, lon_deg, h_m, ve_mps, vn_mps, vu_mps, band);
}
}
}
else if (system == "Galileo")
{
const auto eph_map = pvt_ptr->get_galileo_ephemeris();
const auto eph_it = eph_map.find(prn);
if (eph_it != eph_map.cend() && std::abs(timediff(gps_gtime, eph_to_rtklib(eph_it->second).toe)) <= MAXDTOE_GAL)
{
geometric_doppler_hz = eph_it->second.predicted_doppler(rx_time_s, lat_deg, lon_deg, h_m, ve_mps, vn_mps, vu_mps, band);
}
else
{
const auto alm_map = pvt_ptr->get_galileo_almanac();
const auto alm_it = alm_map.find(prn);
if (alm_it != alm_map.cend() && std::abs(timediff(gps_gtime, alm_to_rtklib(alm_it->second, ref_gps_week).toa)) <= almanac_max_age_s_)
{
geometric_doppler_hz = alm_it->second.predicted_doppler(rx_time_s, lat_deg, lon_deg, h_m, ve_mps, vn_mps, vu_mps, band);
}
}
}
else if (system == "Beidou")
{
const auto eph_map = pvt_ptr->get_beidou_dnav_ephemeris();
const auto eph_it = eph_map.find(prn);
if (eph_it != eph_map.cend() && std::abs(timediff(gps_gtime, eph_to_rtklib(eph_it->second).toe)) <= MAXDTOE_BDS)
{
geometric_doppler_hz = eph_it->second.predicted_doppler(rx_time_s, lat_deg, lon_deg, h_m, ve_mps, vn_mps, vu_mps, band);
}
else
{
const auto alm_map = pvt_ptr->get_beidou_dnav_almanac();
const auto alm_it = alm_map.find(prn);
if (alm_it != alm_map.cend() && std::abs(timediff(gps_gtime, alm_to_rtklib(alm_it->second).toa)) <= almanac_max_age_s_)
{
geometric_doppler_hz = alm_it->second.predicted_doppler(rx_time_s, lat_deg, lon_deg, h_m, ve_mps, vn_mps, vu_mps, band);
}
}
}
if (!geometric_doppler_hz.has_value())
{
return std::nullopt;
}
// Live PVT-solved clock drift, shared by every satellite's observed
// Doppler, on top of the per-satellite geometric term above.
// SUBTRACTED, not added -- empirically verified against
// project_doppler()'s dual-frequency projection (gnss_flowgraph.cc):
// geometric - clock_offset matched to within noise, geometric +
// clock_offset was off by 2x it.
const double clock_offset_hz = fix_status.user_clk_drift_ppm * 1.0e-6 * carrier_freq_hz;
return *geometric_doppler_hz - clock_offset_hz;
}
+17
View File
@@ -26,6 +26,7 @@
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
@@ -161,6 +162,22 @@ public:
bool IsSearchVisible(const Gnss_Satellite& sat) const;
bool IsSearchExcluded(const Gnss_Satellite& sat) const;
// Predicted Doppler (Hz) for sat on the given signal (e.g. "1B", "5X",
// "L5", "7X"), for use as an acquisition search center. Geometric term
// from whichever of sat's ephemeris/almanac is usable (same freshness
// rules as compute_visible_satellites()), plus the receiver's live
// solved clock drift (fix_status.user_clk_drift_ppm) scaled to this
// signal's carrier. Only meaningful with a live PVT fix -- std::nullopt
// when disabled, no fix is currently valid, the signal isn't a
// recognized carrier, or neither ephemeris nor almanac is currently
// usable for sat. No AGNSS-reference fallback yet: that's deferred to a
// follow-up (needs the pre-fix clock/velocity uncertainty this signal's
// Doppler tolerance can't absorb from a single search bin -- see
// GNSS-SDR.clock_frequency_max_error_ppm / receiver_max_velocity_m_s in
// the follow-up PR).
std::optional<double> PredictedDopplerHz(const std::shared_ptr<PvtInterface>& pvt_ptr,
const Monitor_Pvt& fix_status, const Gnss_Satellite& sat, const std::string& signal) const;
private:
enum class SearchVisibility
{