use crate::angle::{Direction, True, TrueCourse};
use crate::error::{ensure_finite, ensure_range, KernelError, NavigationError, Result};
use crate::math;
use crate::units::{Angle, Speed};
use super::{Current, GroundTrack, SteeringSolution};
pub fn course_over_ground(
heading: TrueCourse,
speed_through_water: Speed,
set: TrueCourse,
drift: Speed,
) -> Result<GroundTrack> {
ensure_speed("speed through water", speed_through_water)?;
ensure_speed("drift", drift)?;
let (water_north, water_east) = heading.components(speed_through_water.knots());
let (current_north, current_east) = set.components(drift.knots());
let north = water_north + current_north;
let east = water_east + current_east;
let speed_over_ground = math::hypot(north, east);
if math::is_effectively_zero(
speed_over_ground,
speed_through_water.knots().max(drift.knots()),
) {
return Err(NavigationError::Kernel(KernelError::Missing {
what: "course over ground",
}));
}
Ok(GroundTrack {
course_over_ground: Direction::<True>::from_degrees_wrapped(math::to_degrees(math::atan2(
east, north,
))),
speed_over_ground: Speed::from_knots_unchecked(speed_over_ground),
})
}
pub fn course_to_steer(
track: TrueCourse,
speed_through_water: Speed,
set: TrueCourse,
drift: Speed,
) -> Result<SteeringSolution> {
ensure_speed("speed through water", speed_through_water)?;
ensure_speed("drift", drift)?;
let through_water = speed_through_water.knots();
let current = drift.knots();
let too_strong = || NavigationError::CurrentTooStrong {
drift: current,
speed_through_water: through_water,
};
if through_water < f64::EPSILON {
return Err(too_strong());
}
let current_offset = math::to_radians(track.signed_difference(set));
let sine = -current * math::sin(current_offset) / through_water;
if math::abs(sine) > 1.0 {
return Err(too_strong());
}
let drift_angle = math::asin(sine);
let speed_over_ground =
through_water * math::cos(drift_angle) + current * math::cos(current_offset);
if speed_over_ground <= 0.0 {
return Err(too_strong());
}
Ok(SteeringSolution {
heading: Direction::<True>::from_degrees_wrapped(
track.degrees() + math::to_degrees(drift_angle),
),
speed_over_ground: Speed::from_knots_unchecked(speed_over_ground),
drift_angle: Angle::from_degrees_unchecked(math::to_degrees(drift_angle)),
})
}
pub fn estimate_current(
heading: TrueCourse,
speed_through_water: Speed,
course_over_ground: TrueCourse,
speed_over_ground: Speed,
) -> Result<Current> {
ensure_speed("speed through water", speed_through_water)?;
ensure_speed("speed over ground", speed_over_ground)?;
let (water_north, water_east) = heading.components(speed_through_water.knots());
let (ground_north, ground_east) = course_over_ground.components(speed_over_ground.knots());
let north = ground_north - water_north;
let east = ground_east - water_east;
let drift = math::hypot(north, east);
let set = if math::is_effectively_zero(
drift,
speed_through_water.knots().max(speed_over_ground.knots()),
) {
Direction::<True>::NORTH
} else {
Direction::<True>::from_degrees_wrapped(math::to_degrees(math::atan2(east, north)))
};
Ok(Current {
set,
drift: Speed::from_knots_unchecked(drift),
})
}
pub(super) fn ensure_speed(parameter: &'static str, value: Speed) -> Result<()> {
ensure_finite(parameter, value.knots())?;
Ok(ensure_range(parameter, value.knots(), 0.0, f64::MAX)?)
}