use crate::angle::{wrap180, TrueCourse};
use crate::estimation::{
GatingPolicy, JacobianRow, Observation, ObservationJacobian, ObservationNoise,
ObservationVector,
};
use crate::event::SensorId;
use crate::geodesy::{Ellipsoid, GeodeticPoint, Height};
use crate::gnss::GnssFix;
use crate::local::{LocalFrame, Ned, Vector3};
use crate::math;
use crate::position::Position;
use crate::state::StateComponent::{CurrentEast, CurrentNorth, Heading, SpeedThroughWater};
use crate::state::{NavigationState, StateComponent};
use crate::time::{Instant, Utc};
use crate::units::{Angle, Distance, Speed};
use kinavis_kernel::error::Result;
pub const GATE_ONE_IN_A_THOUSAND_1DOF: f64 = 10.83;
pub const GATE_ONE_IN_A_THOUSAND_2DOF: f64 = 13.82;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PositionObservation {
taken_at: Instant<Utc>,
position: Position,
sigma_north: Distance,
sigma_east: Distance,
gate: GatingPolicy,
sensor: SensorId,
}
impl PositionObservation {
#[must_use]
pub fn new(taken_at: Instant<Utc>, position: Position, sigma: Distance) -> Self {
Self {
taken_at,
position,
sigma_north: sigma,
sigma_east: sigma,
gate: GatingPolicy::reject_above(GATE_ONE_IN_A_THOUSAND_2DOF),
sensor: SensorId::named("position"),
}
}
#[must_use]
pub fn with_sigmas(mut self, north: Distance, east: Distance) -> Self {
self.sigma_north = north;
self.sigma_east = east;
self
}
#[must_use]
pub const fn with_gate(mut self, gate: GatingPolicy) -> Self {
self.gate = gate;
self
}
#[must_use]
pub const fn from_sensor(mut self, sensor: SensorId) -> Self {
self.sensor = sensor;
self
}
#[must_use]
pub fn from_fix(fix: &GnssFix, fallback: Distance) -> Self {
Self::new(
fix.taken_at(),
fix.position(),
fix.horizontal_accuracy().unwrap_or(fallback),
)
}
#[must_use]
pub const fn position(&self) -> Position {
self.position
}
}
impl Observation for PositionObservation {
fn sensor(&self) -> SensorId {
self.sensor
}
fn taken_at(&self) -> Instant<Utc> {
self.taken_at
}
fn measured(&self) -> ObservationVector {
ObservationVector::new(&[0.0, 0.0]).unwrap_or(ObservationVector::EMPTY)
}
fn predict(&self, state: &NavigationState) -> Result<ObservationVector> {
let here = GeodeticPoint::new(self.position, Height::above_ellipsoid(Distance::ZERO));
let frame = LocalFrame::at(here, &Ellipsoid::WGS84)?;
let there = GeodeticPoint::new(state.position(), Height::above_ellipsoid(Distance::ZERO));
let displacement: Vector3<Ned, Distance> = frame.ned_of(there)?;
ObservationVector::new(&[displacement.north().metres(), displacement.east().metres()])
}
fn jacobian(&self, _: &NavigationState) -> Result<ObservationJacobian> {
ObservationJacobian::new()
.with_row(JacobianRow::new().with(StateComponent::North, 1.0))?
.with_row(JacobianRow::new().with(StateComponent::East, 1.0))
}
fn noise(&self) -> ObservationNoise {
sigmas(&[self.sigma_north.metres(), self.sigma_east.metres()])
}
fn gate(&self) -> GatingPolicy {
self.gate
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VelocityObservation {
taken_at: Instant<Utc>,
velocity: Vector3<Ned, Speed>,
sigma: Speed,
gate: GatingPolicy,
sensor: SensorId,
}
impl VelocityObservation {
#[must_use]
pub fn new(taken_at: Instant<Utc>, velocity: Vector3<Ned, Speed>, sigma: Speed) -> Self {
Self {
taken_at,
velocity,
sigma,
gate: GatingPolicy::reject_above(GATE_ONE_IN_A_THOUSAND_2DOF),
sensor: SensorId::named("velocity over ground"),
}
}
#[must_use]
pub fn from_track(
taken_at: Instant<Utc>,
course: TrueCourse,
speed: Speed,
sigma: Speed,
) -> Self {
let radians = math::to_radians(course.degrees());
Self::new(
taken_at,
Vector3::new(
speed * math::cos(radians),
speed * math::sin(radians),
Speed::ZERO,
),
sigma,
)
}
#[must_use]
pub fn from_fix(fix: &GnssFix, sigma: Speed) -> Option<Self> {
Some(Self::from_track(
fix.taken_at(),
fix.course_over_ground()?,
fix.speed_over_ground()?,
sigma,
))
}
#[must_use]
pub const fn with_gate(mut self, gate: GatingPolicy) -> Self {
self.gate = gate;
self
}
#[must_use]
pub const fn from_sensor(mut self, sensor: SensorId) -> Self {
self.sensor = sensor;
self
}
}
impl Observation for VelocityObservation {
fn sensor(&self) -> SensorId {
self.sensor
}
fn taken_at(&self) -> Instant<Utc> {
self.taken_at
}
fn measured(&self) -> ObservationVector {
vector(&[
self.velocity.north().metres_per_second(),
self.velocity.east().metres_per_second(),
])
}
fn predict(&self, state: &NavigationState) -> Result<ObservationVector> {
let velocity = state.velocity_over_ground();
ObservationVector::new(&[
velocity.north().metres_per_second(),
velocity.east().metres_per_second(),
])
}
fn jacobian(&self, state: &NavigationState) -> Result<ObservationJacobian> {
let heading = math::to_radians(state.heading().degrees());
let speed = state.speed_through_water().metres_per_second();
let (sin, cos) = (math::sin(heading), math::cos(heading));
ObservationJacobian::new()
.with_row(
JacobianRow::new()
.with(Heading, -speed * sin)
.with(SpeedThroughWater, cos)
.with(CurrentNorth, 1.0),
)?
.with_row(
JacobianRow::new()
.with(Heading, speed * cos)
.with(SpeedThroughWater, sin)
.with(CurrentEast, 1.0),
)
}
fn noise(&self) -> ObservationNoise {
let sigma = self.sigma.metres_per_second();
sigmas(&[sigma, sigma])
}
fn gate(&self) -> GatingPolicy {
self.gate
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HeadingObservation {
taken_at: Instant<Utc>,
heading: TrueCourse,
sigma: Angle,
gate: GatingPolicy,
sensor: SensorId,
}
impl HeadingObservation {
#[must_use]
pub fn new(taken_at: Instant<Utc>, heading: TrueCourse, sigma: Angle) -> Self {
Self {
taken_at,
heading,
sigma,
gate: GatingPolicy::reject_above(GATE_ONE_IN_A_THOUSAND_1DOF),
sensor: SensorId::named("heading"),
}
}
#[must_use]
pub const fn with_gate(mut self, gate: GatingPolicy) -> Self {
self.gate = gate;
self
}
#[must_use]
pub const fn from_sensor(mut self, sensor: SensorId) -> Self {
self.sensor = sensor;
self
}
}
impl Observation for HeadingObservation {
fn sensor(&self) -> SensorId {
self.sensor
}
fn taken_at(&self) -> Instant<Utc> {
self.taken_at
}
fn measured(&self) -> ObservationVector {
vector(&[math::to_radians(self.heading.degrees())])
}
fn predict(&self, state: &NavigationState) -> Result<ObservationVector> {
ObservationVector::new(&[math::to_radians(state.heading().degrees())])
}
fn jacobian(&self, _: &NavigationState) -> Result<ObservationJacobian> {
ObservationJacobian::new().with_row(JacobianRow::new().with(StateComponent::Heading, 1.0))
}
fn noise(&self) -> ObservationNoise {
sigmas(&[self.sigma.radians()])
}
fn gate(&self) -> GatingPolicy {
self.gate
}
fn innovation(&self, predicted: &ObservationVector) -> Result<ObservationVector> {
let measured = math::to_radians(self.heading.degrees());
let predicted = predicted.get(0).unwrap_or(0.0);
let difference = math::to_radians(wrap180(math::to_degrees(measured - predicted)));
ObservationVector::new(&[difference])
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpeedThroughWaterObservation {
taken_at: Instant<Utc>,
speed: Speed,
sigma: Speed,
gate: GatingPolicy,
sensor: SensorId,
}
impl SpeedThroughWaterObservation {
#[must_use]
pub fn new(taken_at: Instant<Utc>, speed: Speed, sigma: Speed) -> Self {
Self {
taken_at,
speed,
sigma,
gate: GatingPolicy::reject_above(GATE_ONE_IN_A_THOUSAND_1DOF),
sensor: SensorId::named("speed through water"),
}
}
#[must_use]
pub const fn with_gate(mut self, gate: GatingPolicy) -> Self {
self.gate = gate;
self
}
#[must_use]
pub const fn from_sensor(mut self, sensor: SensorId) -> Self {
self.sensor = sensor;
self
}
}
impl Observation for SpeedThroughWaterObservation {
fn sensor(&self) -> SensorId {
self.sensor
}
fn taken_at(&self) -> Instant<Utc> {
self.taken_at
}
fn measured(&self) -> ObservationVector {
vector(&[self.speed.metres_per_second()])
}
fn predict(&self, state: &NavigationState) -> Result<ObservationVector> {
ObservationVector::new(&[state.speed_through_water().metres_per_second()])
}
fn jacobian(&self, _: &NavigationState) -> Result<ObservationJacobian> {
ObservationJacobian::new()
.with_row(JacobianRow::new().with(StateComponent::SpeedThroughWater, 1.0))
}
fn noise(&self) -> ObservationNoise {
sigmas(&[self.sigma.metres_per_second()])
}
fn gate(&self) -> GatingPolicy {
self.gate
}
}
fn vector(values: &[f64]) -> ObservationVector {
ObservationVector::new(values).unwrap_or(ObservationVector::EMPTY)
}
fn sigmas(values: &[f64]) -> ObservationNoise {
let floor = |sigma: &f64| sigma.max(1e-9);
let mut floored = [0.0; 4];
for (slot, sigma) in floored.iter_mut().zip(values) {
*slot = floor(sigma);
}
ObservationNoise::sigmas(floored.get(..values.len().min(4)).unwrap_or(&[]))
.unwrap_or(ObservationNoise::UNIT)
}