93 lines
2.1 KiB
Go
93 lines
2.1 KiB
Go
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)
|
|
}
|