package doppler import ( "fmt" "math" "sync" "time" "SatMaster/backend/propagator" ) const ( SpeedOfLight = 299792.458 // km/s ) // Calculator computes Doppler-shifted frequencies. type Calculator struct { mu sync.RWMutex nominalDown float64 // Hz nominalUp float64 // Hz obsLat float64 obsLon float64 obsAlt float64 } func NewCalculator() *Calculator { return &Calculator{} } func (c *Calculator) SetObserver(lat, lon, altM float64) { c.mu.Lock() defer c.mu.Unlock() c.obsLat = lat c.obsLon = lon c.obsAlt = altM } func (c *Calculator) SetNominal(downHz, upHz float64) { c.mu.Lock() defer c.mu.Unlock() c.nominalDown = downHz c.nominalUp = upHz } // Correct computes Doppler-corrected downlink and uplink frequencies. // Returns (downlinkHz, uplinkHz). func (c *Calculator) Correct(pos *propagator.SatPosition, obs propagator.Observer, _ time.Time) (float64, float64) { c.mu.RLock() nomDown := c.nominalDown nomUp := c.nominalUp c.mu.RUnlock() if nomDown == 0 && nomUp == 0 { return 0, 0 } if pos == nil { return nomDown, nomUp } // Range rate in km/s (positive = receding, negative = approaching) rr := pos.RangeRate // Doppler factor: f_received = f_nominal * (1 - v/c) // For downlink: satellite is the transmitter dopplerFactor := 1.0 - rr/SpeedOfLight correctedDown := nomDown * dopplerFactor // For uplink: we pre-correct in reverse so the satellite receives nominal correctedUp := nomUp / dopplerFactor return correctedDown, correctedUp } // ShiftHz returns the Doppler shift in Hz for a given nominal frequency. func ShiftHz(nominalHz, rangeRateKmS float64) float64 { return nominalHz * (-rangeRateKmS / SpeedOfLight) } // RangeRateFromPositions computes range rate from two consecutive positions. func RangeRateFromPositions(prev, curr *propagator.SatPosition, dt float64) float64 { prevRange := prev.Range currRange := curr.Range return (currRange - prevRange) / dt } // FormatShift formats a Doppler shift in Hz for display. func FormatShift(shiftHz float64) string { if math.Abs(shiftHz) >= 1000 { return fmt.Sprintf("%+.2f kHz", shiftHz/1000) } return fmt.Sprintf("%+.0f Hz", shiftHz) }