use crate::angle::{
wrap180, Compass, Deviation, Direction, Frame, Gyro, GyroCourse, Magnetic, MagneticCourse,
RelativeBearing, True, TrueCourse, Variation,
};
use crate::error::{ensure_range, KernelError, NavigationError, Result};
use crate::math;
use crate::position::Latitude;
use crate::units::{Angle, Speed};
use super::current::ensure_speed;
use super::MAX_GYRO_LATITUDE_DEG;
const EARTH_SURFACE_SPEED_KNOTS: f64 = 900.0;
#[must_use]
pub fn magnetic_to_true(direction: MagneticCourse, variation: Variation) -> TrueCourse {
Direction::<True>::from_degrees_wrapped(direction.degrees() + variation.degrees())
}
#[must_use]
pub fn true_to_magnetic(direction: TrueCourse, variation: Variation) -> MagneticCourse {
Direction::<Magnetic>::from_degrees_wrapped(direction.degrees() - variation.degrees())
}
#[must_use]
pub fn compass_to_magnetic(
direction: Direction<Compass>,
deviation: Deviation,
) -> Direction<Magnetic> {
Direction::<Magnetic>::from_degrees_wrapped(direction.degrees() + deviation.degrees())
}
#[must_use]
pub fn magnetic_to_compass(
direction: Direction<Magnetic>,
deviation: Deviation,
) -> Direction<Compass> {
Direction::<Compass>::from_degrees_wrapped(direction.degrees() - deviation.degrees())
}
#[must_use]
pub fn gyro_to_true(direction: GyroCourse, error: Angle) -> TrueCourse {
Direction::<True>::from_degrees_wrapped(direction.degrees() + error.degrees())
}
#[must_use]
pub fn true_to_gyro(direction: TrueCourse, error: Angle) -> GyroCourse {
Direction::<Gyro>::from_degrees_wrapped(direction.degrees() - error.degrees())
}
#[must_use]
pub fn gyro_error_from_transit(observed: Direction<Gyro>, reference: Direction<True>) -> Angle {
Angle::from_degrees_unchecked(wrap180(reference.degrees() - observed.degrees()))
}
pub fn gyro_speed_error(latitude: Latitude, course: TrueCourse, speed: Speed) -> Result<Angle> {
ensure_speed("speed", speed)?;
ensure_range(
"latitude",
latitude.degrees(),
-MAX_GYRO_LATITUDE_DEG,
MAX_GYRO_LATITUDE_DEG,
)?;
let knots = speed.knots();
let course_radians = course.radians();
let eastward = EARTH_SURFACE_SPEED_KNOTS * math::cos(latitude.radians())
+ knots * math::sin(course_radians);
if eastward <= f64::EPSILON {
return Err(NavigationError::Kernel(KernelError::Indeterminate {
quantity: "the settling meridian of a gyrocompass at this latitude",
}));
}
let displacement = math::atan2(knots * math::cos(course_radians), eastward);
Ok(Angle::from_degrees_unchecked(-math::to_degrees(
displacement,
)))
}
#[must_use]
pub fn calculate_course_angle<F: Frame>(
course: Direction<F>,
bearing: Direction<F>,
) -> RelativeBearing {
RelativeBearing::from_degrees_wrapped(bearing.degrees() - course.degrees())
}
#[must_use]
pub fn bearing_from_relative<F: Frame>(
course: Direction<F>,
relative: RelativeBearing,
) -> Direction<F> {
Direction::<F>::from_degrees_wrapped(course.degrees() + relative.degrees())
}