use crate::angle::{CardinalPoint, CompassCourse, Deviation, Variation};
use crate::error::{KernelError, NavigationError, Result};
use crate::math;
use crate::inline::Inline;
use super::analysis;
use super::interpolation::{self, Interpolation, Interpolator};
use super::node::DeviationNode;
use super::swing::SwingObservation;
pub const STANDARD_TABLE_LEN: usize = 36;
pub const MAX_TABLE_NODES: usize = STANDARD_TABLE_LEN * 2;
type Nodes = Inline<DeviationNode, MAX_TABLE_NODES>;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "Vec<(i32, f64)>", into = "Vec<(i32, f64)>")
)]
pub struct DeviationTable {
nodes: Nodes,
}
impl Default for DeviationTable {
fn default() -> Self {
let mut nodes = Nodes::new(DeviationNode::zero_at(0));
for index in 0..STANDARD_TABLE_LEN {
let _ = nodes.push(DeviationNode::zero_at(
i32::try_from(index).unwrap_or(0) * 10,
));
}
Self { nodes }
}
}
impl DeviationTable {
pub fn from_step(step: i32) -> Result<Self> {
if !(1..=180).contains(&step) {
return Err(NavigationError::InvalidStep { step });
}
let stride = usize::try_from(step).unwrap_or(1);
let mut nodes = Nodes::new(DeviationNode::zero_at(0));
for course in (0..360).step_by(stride) {
push_node(&mut nodes, DeviationNode::zero_at(course), 360 / stride)?;
}
Ok(Self { nodes })
}
#[must_use]
pub fn from_cardinal_directions() -> Self {
let mut nodes = Nodes::new(DeviationNode::zero_at(0));
for point in CardinalPoint::ALL {
let _ = nodes.push(DeviationNode::zero_at(point.whole_degrees()));
}
nodes
.as_mut_slice()
.sort_unstable_by_key(DeviationNode::course);
Self { nodes }
}
pub fn from_pairs(deviations: &[(i32, f64)]) -> Result<Self> {
let mut nodes = Nodes::new(DeviationNode::zero_at(0));
for &(course, deviation) in deviations {
push_node(
&mut nodes,
DeviationNode::new(course, deviation)?,
deviations.len(),
)?;
}
Self::from_nodes(nodes)
}
pub fn from_deviations(deviations: &[f64]) -> Result<Self> {
if deviations.len() != STANDARD_TABLE_LEN {
return Err(NavigationError::UnexpectedTableLength {
found: deviations.len(),
expected: STANDARD_TABLE_LEN,
});
}
let mut nodes = Nodes::new(DeviationNode::zero_at(0));
for (index, &deviation) in deviations.iter().enumerate() {
let node = DeviationNode::new(i32::try_from(index).unwrap_or(0) * 10, deviation)?;
push_node(&mut nodes, node, STANDARD_TABLE_LEN)?;
}
Self::from_nodes(nodes)
}
pub fn from_swing(observations: &[SwingObservation], variation: Variation) -> Result<Self> {
let mut nodes = Nodes::new(DeviationNode::zero_at(0));
for observation in observations {
let deviation = observation.deviation(variation)?;
let heading = math::round_to_i32(observation.compass_heading.degrees());
push_node(
&mut nodes,
DeviationNode::new(heading, deviation.degrees())?,
observations.len(),
)?;
}
Self::from_nodes(nodes)
}
fn from_nodes(mut nodes: Nodes) -> Result<Self> {
nodes
.as_mut_slice()
.sort_unstable_by_key(DeviationNode::course);
if let Some(duplicate) = nodes
.windows(2)
.find(|pair| {
pair.first().map(DeviationNode::course) == pair.last().map(DeviationNode::course)
})
.and_then(|pair| pair.first())
{
return Err(NavigationError::DuplicateCourse {
course: duplicate.course(),
});
}
if nodes.len() < 2 {
return Err(NavigationError::Kernel(KernelError::InsufficientData {
found: nodes.len(),
required: 2,
context: "a deviation table",
}));
}
Ok(Self { nodes })
}
#[must_use]
pub fn nodes(&self) -> &[DeviationNode] {
&self.nodes
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn set_deviation(&mut self, course: i32, deviation: Deviation) -> Result<()> {
let course = course.rem_euclid(360);
match self
.nodes
.binary_search_by_key(&course, DeviationNode::course)
{
Ok(index) => {
if let Some(node) = self.nodes.as_mut_slice().get_mut(index) {
node.set_deviation_degrees(deviation.degrees());
}
Ok(())
}
Err(_) => Err(NavigationError::CourseNotInTable { course }),
}
}
pub fn insert_deviation(&mut self, course: i32, deviation: Deviation) -> Result<()> {
let course = course.rem_euclid(360);
match self
.nodes
.binary_search_by_key(&course, DeviationNode::course)
{
Ok(index) => {
if let Some(node) = self.nodes.as_mut_slice().get_mut(index) {
node.set_deviation_degrees(deviation.degrees());
}
}
Err(index) => {
let node = DeviationNode::with_deviation(course, deviation);
let needed = self.nodes.len().saturating_add(1);
self.nodes.insert(index, node).map_err(|full| {
NavigationError::Kernel(KernelError::CapacityExceeded {
context: "a deviation table",
needed,
capacity: full.capacity,
})
})?;
}
}
Ok(())
}
pub fn set_deviation_at(&mut self, point: CardinalPoint, deviation: Deviation) -> Result<()> {
self.set_deviation(point.whole_degrees(), deviation)
}
pub fn deviation_at_point(&self, point: CardinalPoint) -> Result<Deviation> {
let course = point.whole_degrees();
self.deviation_at_node(course)
.ok_or(NavigationError::CourseNotInTable { course })
}
#[must_use]
pub fn deviation_at_node(&self, course: i32) -> Option<Deviation> {
let course = course.rem_euclid(360);
self.nodes
.binary_search_by_key(&course, DeviationNode::course)
.ok()
.and_then(|index| self.nodes.get(index))
.map(DeviationNode::deviation)
}
#[must_use]
pub fn max_gap(&self) -> f64 {
analysis::max_gap(&self.nodes)
}
#[must_use]
pub fn max_slope(&self) -> f64 {
analysis::max_slope(&self.nodes)
}
#[must_use]
pub fn is_invertible(&self) -> bool {
analysis::is_invertible(&self.nodes)
}
#[must_use]
pub fn max_abs_deviation(&self) -> f64 {
analysis::max_abs_deviation(&self.nodes)
}
pub fn deviation_at<'a>(
&self,
course: CompassCourse,
interpolation: impl Into<Interpolation<'a>>,
) -> Result<Deviation> {
let interpolator = interpolation::prepare(self, interpolation.into())?;
Ok(Deviation::new(interpolator.evaluate(course.degrees()))?)
}
pub fn interpolate_deviation<'a>(
&self,
courses: &[CompassCourse],
interpolation: impl Into<Interpolation<'a>>,
out: &mut [Deviation],
) -> Result<()> {
if out.len() < courses.len() {
return Err(NavigationError::Kernel(KernelError::BufferTooSmall {
needed: courses.len(),
found: out.len(),
}));
}
let interpolator = interpolation::prepare(self, interpolation.into())?;
for (course, slot) in courses.iter().zip(out.iter_mut()) {
*slot = Deviation::new(interpolator.evaluate(course.degrees()))?;
}
Ok(())
}
}
#[cfg(feature = "serde")]
use alloc::vec::Vec;
#[cfg(feature = "serde")]
impl TryFrom<Vec<(i32, f64)>> for DeviationTable {
type Error = NavigationError;
fn try_from(nodes: Vec<(i32, f64)>) -> Result<Self> {
Self::from_pairs(&nodes)
}
}
#[cfg(feature = "serde")]
impl From<DeviationTable> for Vec<(i32, f64)> {
fn from(table: DeviationTable) -> Self {
table
.nodes
.iter()
.map(|node| (node.course(), node.deviation_degrees()))
.collect()
}
}
#[cfg(test)]
#[path = "table_tests.rs"]
mod tests;
fn push_node(nodes: &mut Nodes, node: DeviationNode, needed: usize) -> Result<()> {
nodes.push(node).map_err(|full| {
NavigationError::Kernel(KernelError::CapacityExceeded {
context: "a deviation table",
needed,
capacity: full.capacity,
})
})
}