velocity

package
v0.34.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 2, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ComputeElevationWeight

func ComputeElevationWeight(elevationDeg float64, exponent float64) float64

ComputeElevationWeight calculates the elevation-dependent weight for a satellite. Weight = sin(elevation)^exponent This gives higher weight to satellites at higher elevations which typically have better signal quality and less multipath.

func CountValidObservables

func CountValidObservables(states []*pipeline.SatelliteState) int

CountValidObservables returns the number of satellites with valid TDCP observables. This is a utility method for logging and diagnostics.

func FilterByQuality

func FilterByQuality(states []*pipeline.SatelliteState, config TDCPConfig) []*pipeline.SatelliteState

FilterByQuality filters satellite states based on quality criteria. This can be used to exclude satellites with: - Detected cycle slips - Low elevation - Health issues - etc.

Returns a new slice containing only satellites that pass all quality checks.

func ValidateContinuity

func ValidateContinuity(
	current *pipeline.SatelliteState,
	previous *pipeline.SatelliteState,
	detector *CycleSlipDetector,
) bool

ValidateContinuity checks if two satellite states represent continuous tracking. This is a helper for TDCP processing to determine if epoch-to-epoch differencing is valid.

Returns true if: - No cycle slip is detected - Satellite is tracked in both epochs - Required observations are available

Types

type CombinationType

type CombinationType int

CombinationType specifies which carrier phase combination to use for TDCP.

const (
	// CombinationIF uses the ionosphere-free (L1-L2) combination.
	// This eliminates first-order ionospheric effects and is the recommended default.
	CombinationIF CombinationType = iota

	// CombinationNL uses the narrow-lane combination.
	// This has lower noise but is affected by ionosphere.
	CombinationNL
)

func (CombinationType) String

func (c CombinationType) String() string

String returns a human-readable name for the combination type.

type ConfigError

type ConfigError struct {
	Field   string
	Message string
}

ConfigError represents a configuration validation error.

func (*ConfigError) Error

func (e *ConfigError) Error() string

Error implements the error interface.

type CycleSlipDetector

type CycleSlipDetector struct {
	// contains filtered or unexported fields
}

CycleSlipDetector detects cycle slips using multiple methods. Phase 1: LLI flags and Melbourne-Wübbena differencing Phase 2: Magnitude estimation (planned)

func NewCycleSlipDetector

func NewCycleSlipDetector(mwThreshold float64) *CycleSlipDetector

NewCycleSlipDetector creates a new cycle slip detector with the specified threshold.

func (*CycleSlipDetector) DetectSlip

DetectSlip checks for cycle slips between current and previous satellite states. It returns a CycleSlipInfo struct with detection results.

Detection methods (in order of priority): 1. Loss of Lock Indicator (LLI) flags from observations (both epochs) 2. Melbourne-Wübbena combination difference

Returns nil if no slip is detected.

func (*CycleSlipDetector) EstimateSlipMagnitude

func (d *CycleSlipDetector) EstimateSlipMagnitude(
	current *pipeline.SatelliteState,
	previous *pipeline.SatelliteState,
	slipInfo *pipeline.CycleSlipInfo,
) *float64

EstimateSlipMagnitude attempts to quantify the cycle slip in cycles. This is a Phase 2 feature - placeholder for future implementation.

The basic approach would be: 1. Compare predicted phase (from geometry) to measured phase 2. Compute the difference and quantize to nearest integer cycle 3. Verify consistency across frequencies

Returns the estimated slip in cycles, or nil if estimation fails.

type EKFConfig

type EKFConfig struct {

	// Position process noise [m/√s]
	// Controls how much the position is allowed to change between epochs
	// Typical value: 0.1 (10 cm/√s)
	PositionStdDev float64

	// Velocity process noise [(m/s)/√s]
	// Controls how much the velocity is allowed to change between epochs
	// Typical value: 0.01 (1 cm/s/√s)
	VelocityStdDev float64

	// Clock offset process noise [m/√s]
	// Typical value: 100 (for typical receiver clocks)
	ClockStdDev float64

	// Clock drift process noise [(m/s)/√s]
	// Typical value: 0.1
	ClockDriftStdDev float64

	// Pseudorange measurement noise [m]
	// Typical value: 1.0 meter
	PseudorangeSigma float64

	// Carrier phase measurement noise [m]
	// Typical value: 0.01 meters (1 cm)
	CarrierPhaseSigma float64
}

EKFConfig contains Extended Kalman Filter-specific parameters. These are only used when SolverType == SolverEKF.

func DefaultEKFConfig

func DefaultEKFConfig() EKFConfig

DefaultEKFConfig returns EKF parameters with recommended default values.

func (*EKFConfig) Validate

func (e *EKFConfig) Validate() error

Validate checks if the EKF configuration is valid.

type ENUVelocity

type ENUVelocity struct {
	East  float64 // East component [m/s]
	North float64 // North component [m/s]
	Up    float64 // Up component [m/s]
}

ENUVelocity represents velocity in the local East-North-Up coordinate frame.

type EpochBuffer

type EpochBuffer struct {
	// contains filtered or unexported fields
}

EpochBuffer maintains a thread-safe 2-epoch sliding window for TDCP differencing. It stores satellite states from the previous epoch to enable epoch-to-epoch carrier phase differencing for velocity estimation.

func NewEpochBuffer

func NewEpochBuffer() *EpochBuffer

NewEpochBuffer creates a new epoch buffer.

func (*EpochBuffer) Clear

func (b *EpochBuffer) Clear()

Clear resets the epoch buffer, removing all stored states. This is useful when starting a new processing session or recovering from errors.

func (*EpochBuffer) Count

func (b *EpochBuffer) Count() int

Count returns the number of satellites in the previous epoch. This can be useful for diagnostics and logging.

func (*EpochBuffer) GetCurrentTime

func (b *EpochBuffer) GetCurrentTime() time.Time

GetCurrentTime returns the time of the current epoch. This is the time of the most recent UpdateEpoch call.

func (*EpochBuffer) GetPrevious

GetPrevious retrieves the satellite state from the previous epoch. Returns nil if the satellite was not present in the previous epoch. The returned state is a pointer to the stored state - callers should not modify it.

func (*EpochBuffer) HasPrevious

func (b *EpochBuffer) HasPrevious(satKey observation.SatelliteKey) bool

HasPrevious checks if a satellite exists in the previous epoch. This is useful for determining continuity before attempting TDCP processing.

func (*EpochBuffer) UpdateEpoch

func (b *EpochBuffer) UpdateEpoch(states []*pipeline.SatelliteState, epochTime time.Time)

UpdateEpoch advances the epoch window, storing the current epoch states as the new "previous" epoch for the next processing cycle. This method stores pointers to the given states and does not perform a deep copy.

type GeometryFreeData

type GeometryFreeData struct {
	System      gnss.System
	SatelliteID observation.SatelliteID
	Elevation   float64 // Elevation [degrees]
	Azimuth     float64 // Azimuth [degrees]
	PPLatitude  float64 // Ionospheric pierce point latitude [degrees]
	PPLongitude float64 // Ionospheric pierce point longitude [degrees]
	PPHeight    float64 // Ionospheric pierce point height [meters]
	Phase       float64 // Rate of change of geometry-free phase [meters/interval]
	Range       float64 // Rate of change of geometry-free range [meters/interval]
	TEC         float64 // Rate of change of slant TEC [TECU/interval]
	TECFiltered float64 // High-pass filtered TEC rate [TECU/interval]
}

GeometryFreeData contains geometry-free TEC rate information for a single satellite. This is useful for ionospheric monitoring and analysis.

type LeastSquaresSolver

type LeastSquaresSolver struct {
	// contains filtered or unexported fields
}

LeastSquaresSolver implements weighted least squares velocity estimation for TDCP. This is a stateless solver that processes epoch-by-epoch.

func NewLeastSquaresSolver

func NewLeastSquaresSolver(config TDCPConfig) *LeastSquaresSolver

NewLeastSquaresSolver creates a new least squares solver with the given configuration.

func (*LeastSquaresSolver) Solve

func (s *LeastSquaresSolver) Solve(
	states []*pipeline.SatelliteState,
	epochTime time.Time,
	timeDelta time.Duration,
) (*VelocitySolution, error)

Solve computes the velocity solution from TDCP observables using weighted least squares.

The observation equation is: y - b = A * x Where:

y = measured delta-phase observations [meters]
b = modeled range changes [meters]
A = geometry matrix, each row is [dx/rho, dy/rho, dz/rho, 1]
x = unknowns [Δvx, Δvy, Δvz, Δclock] (velocity deltas over time interval)

Solution: x = (A'WA)^-1 A'W(y-b) Where W is a diagonal weight matrix based on satellite elevation.

type SolverType

type SolverType int

SolverType specifies the algorithm used for velocity estimation.

const (
	// SolverLS uses weighted least squares for velocity estimation.
	// This is a simple, stateless approach suitable for epoch-by-epoch processing.
	SolverLS SolverType = iota

	// SolverEKF uses an Extended Kalman Filter for velocity estimation.
	// This provides smoothing and can fuse SPP position with TDCP velocity.
	// Planned for Phase 3 implementation.
	SolverEKF
)

func (SolverType) String

func (s SolverType) String() string

String returns a human-readable name for the solver type.

type TDCPConfig

type TDCPConfig struct {
	// Solver method to use for velocity estimation
	SolverType SolverType

	// Carrier phase combination to use for delta-phase computation
	Combination CombinationType

	// Cycle slip detection threshold for Melbourne-Wübbena combination [meters]
	// Default: 0.5 meters
	MWSlipThreshold float64

	// Enable cycle slip magnitude estimation (Phase 2 feature)
	// When enabled, the processor will attempt to quantify the slip in cycles
	EnableSlipEstimation bool

	// Output configuration
	OutputResiduals    bool // Include post-fit residuals in results
	OutputGeometryFree bool // Include geometry-free TEC rate data (Phase 4 feature)

	// EKF-specific configuration (used when SolverType == SolverEKF)
	// Phase 3 feature
	EKFConfig EKFConfig

	// Receiver position for geometry calculations and ENU conversion
	// This must be provided for TDCP processing
	ReceiverPosition coordinates.Vector3D

	// Minimum number of satellites required for a solution
	// Default: 4 (minimum for 3D velocity + clock drift)
	MinSatellites int

	// Elevation weighting exponent
	// Weight = sin(elevation)^WeightExponent
	// Default: 2.0 (standard elevation-dependent weighting)
	WeightExponent float64
}

TDCPConfig contains all configuration parameters for TDCP velocity processing.

func DefaultTDCPConfig

func DefaultTDCPConfig() TDCPConfig

DefaultTDCPConfig returns a TDCP configuration with recommended default values.

func (*TDCPConfig) Validate

func (c *TDCPConfig) Validate() error

Validate checks if the configuration is valid and returns an error if not.

type TDCPEphemerisProcessor

type TDCPEphemerisProcessor struct {
	// contains filtered or unexported fields
}

TDCPEphemerisProcessor is a specialized ephemeris processor for TDCP that ensures consistent ephemeris usage across consecutive epochs.

Unlike the standard EphemerisProcessor which looks up ephemeris at each epoch's time, this processor uses a reference time (from the previous epoch) to look up ephemeris for the current epoch. This matches the legacy TDCP behavior and prevents noise from ephemeris message changes between closely-spaced epochs.

func NewTDCPEphemerisProcessor

func NewTDCPEphemerisProcessor(store *ephemeris.EphemerisStore) *TDCPEphemerisProcessor

NewTDCPEphemerisProcessor creates a new TDCP ephemeris processor.

func (*TDCPEphemerisProcessor) GetReferenceTime

func (p *TDCPEphemerisProcessor) GetReferenceTime() time.Time

GetReferenceTime returns the current reference time.

func (*TDCPEphemerisProcessor) Name

func (p *TDCPEphemerisProcessor) Name() string

Name returns the processor name for logging and error messages.

func (*TDCPEphemerisProcessor) Process

Process looks up and attaches ephemeris to the satellite state using a consistent reference time.

func (*TDCPEphemerisProcessor) UpdateReferenceTime

func (p *TDCPEphemerisProcessor) UpdateReferenceTime(t time.Time)

UpdateReferenceTime updates the reference time for ephemeris lookups. This should be called after processing an epoch to set the reference time for the next epoch.

type TDCPObservableProcessor

type TDCPObservableProcessor struct {
	// contains filtered or unexported fields
}

TDCPObservableProcessor computes TDCP (Time-Differenced Carrier Phase) observables for each satellite. This is a stateful processor that maintains an epoch buffer to enable epoch-to-epoch carrier phase differencing.

Processing steps: 1. Check if satellite exists in previous epoch (continuity) 2. Detect cycle slips via LLI flags and Melbourne-Wübbena 3. Compute delta-phase from ionosphere-free or narrow-lane combination 4. Compute geometry matrix row (unit vector to satellite) 5. Compute modeled range change (geometry + corrections) 6. Compute elevation-based weight 7. Store VelocityObservable in state.Velocity 8. Store CycleSlipInfo in state.CycleSlip

func NewTDCPObservableProcessor

func NewTDCPObservableProcessor(config TDCPConfig) *TDCPObservableProcessor

NewTDCPObservableProcessor creates a new TDCP observable processor.

func (*TDCPObservableProcessor) GetEpochBuffer

func (p *TDCPObservableProcessor) GetEpochBuffer() *EpochBuffer

GetEpochBuffer returns the internal epoch buffer for testing or advanced usage.

func (*TDCPObservableProcessor) Name

func (p *TDCPObservableProcessor) Name() string

Name returns the processor name for logging and debugging.

func (*TDCPObservableProcessor) Process

Process implements the pipeline.Processor interface. It computes TDCP observables for a single satellite by differencing with the previous epoch.

func (*TDCPObservableProcessor) UpdateEpoch

func (p *TDCPObservableProcessor) UpdateEpoch(states []*pipeline.SatelliteState, epochTime time.Time)

UpdateEpoch advances the epoch buffer to prepare for the next epoch. This should be called after all satellites in the current epoch have been processed.

type TDCPSolverProcessor

type TDCPSolverProcessor struct {
	// contains filtered or unexported fields
}

TDCPSolverProcessor aggregates TDCP observables from all satellites and computes the velocity solution.

NOTE: This processor does NOT implement the standard pipeline.Processor interface because it requires access to ALL satellites, not just one at a time. It is invoked separately after the pipeline has processed all satellites.

func NewTDCPSolverProcessor

func NewTDCPSolverProcessor(config TDCPConfig) *TDCPSolverProcessor

NewTDCPSolverProcessor creates a new TDCP solver processor.

func (*TDCPSolverProcessor) GetConfig

func (p *TDCPSolverProcessor) GetConfig() TDCPConfig

GetConfig returns the current configuration. This can be useful for inspecting or modifying the configuration.

func (*TDCPSolverProcessor) ProcessEpoch

func (p *TDCPSolverProcessor) ProcessEpoch(
	ctx context.Context,
	states []*pipeline.SatelliteState,
	epochTime time.Time,
) (*VelocitySolution, error)

ProcessEpoch computes the velocity solution from all satellite states. This method should be called after the pipeline has processed all satellites in the current epoch.

The context parameter can be used for cancellation. The states parameter should contain all satellites from the current epoch. The epochTime parameter is the timestamp of the current epoch.

Returns the velocity solution or an error if the solution cannot be computed.

func (*TDCPSolverProcessor) UpdateConfig

func (p *TDCPSolverProcessor) UpdateConfig(config TDCPConfig) error

UpdateConfig updates the solver configuration. Note: This will recreate internal solvers, so use with caution.

type VelocityResidual

type VelocityResidual struct {
	System      gnss.System
	SatelliteID observation.SatelliteID
	Residual    float64 // Post-fit residual [meters]
	Elevation   float64 // Elevation [degrees]
	Azimuth     float64 // Azimuth [degrees]
}

VelocityResidual contains post-fit residual information for a single satellite.

type VelocitySolution

type VelocitySolution struct {
	// Timestamp of the current epoch
	Timestamp time.Time

	// Time interval between epochs used for differencing
	TimeDelta time.Duration

	// Velocity in ECEF (Earth-Centered Earth-Fixed) frame [m/s]
	ECEF coordinates.Vector3D

	// Velocity in local ENU (East-North-Up) frame [m/s]
	ENU ENUVelocity

	// Receiver clock drift [m/s]
	// This is the rate of change of the receiver clock offset
	ClockDrift float64

	// Number of satellites used in the solution
	SatelliteCount int

	// Post-fit residuals for each satellite
	// Only populated if OutputResiduals is enabled in configuration
	Residuals []VelocityResidual

	// Geometry-free TEC rate data for each satellite
	// Only populated if OutputGeometryFree is enabled in configuration
	GeometryFree []GeometryFreeData

	// Optional: Filtered position estimate (for EKF solver)
	// This is the position state from the Kalman filter when using SPP+TDCP fusion
	FilteredPosition *coordinates.Vector3D
}

VelocitySolution contains the complete velocity estimation result. This is the primary output type from the TDCP solver.

func (*VelocitySolution) ToLegacyGeometryFree

func (v *VelocitySolution) ToLegacyGeometryFree(streamID string) *tdcp.GeometryFreeOutput

ToLegacyGeometryFree converts the modern geometry-free data to the legacy tdcp.GeometryFreeOutput type. Returns nil if no geometry-free data is present.

func (*VelocitySolution) ToLegacyResiduals

func (v *VelocitySolution) ToLegacyResiduals(streamID string) *tdcp.Residuals

ToLegacyResiduals converts the modern residuals to the legacy tdcp.Residuals type. Returns nil if no residuals are present.

func (*VelocitySolution) ToLegacyVelocity

func (v *VelocitySolution) ToLegacyVelocity(streamID string) tdcp.Velocity

ToLegacyVelocity converts the modern VelocitySolution to the legacy tdcp.Velocity type. This enables backward compatibility with code using the old TDCP API. The streamID parameter is application-specific and not part of the core velocity data.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL