use num_traits::FromPrimitive;
use super::super::{Bearing, Destination, Distance, InterpolatePoint};
use crate::rhumb::RhumbCalculations;
use crate::{CoordFloat, MEAN_EARTH_RADIUS, Point};
pub struct Rhumb;
impl<F: CoordFloat + FromPrimitive> Bearing<F> for Rhumb {
fn bearing(&self, origin: Point<F>, destination: Point<F>) -> F {
let three_sixty = F::from(360.0f64).unwrap();
let calculations = RhumbCalculations::new(&origin, &destination);
(calculations.theta().to_degrees() + three_sixty) % three_sixty
}
}
impl<F: CoordFloat + FromPrimitive> Destination<F> for Rhumb {
fn destination(&self, origin: Point<F>, bearing: F, distance: F) -> Point<F> {
let delta = distance / F::from(MEAN_EARTH_RADIUS).unwrap(); let lambda1 = origin.x().to_radians();
let phi1 = origin.y().to_radians();
let theta = bearing.to_radians();
crate::algorithm::rhumb::calculate_destination(delta, lambda1, phi1, theta)
}
}
impl<F: CoordFloat + FromPrimitive> Distance<F, Point<F>, Point<F>> for Rhumb {
fn distance(&self, origin: Point<F>, destination: Point<F>) -> F {
let calculations = RhumbCalculations::new(&origin, &destination);
calculations.delta() * F::from(MEAN_EARTH_RADIUS).unwrap()
}
}
impl<F: CoordFloat + FromPrimitive> InterpolatePoint<F> for Rhumb {
fn point_at_distance_between(
&self,
start: Point<F>,
end: Point<F>,
meters_from_start: F,
) -> Point<F> {
let bearing = Self.bearing(start, end);
Self.destination(start, bearing, meters_from_start)
}
fn point_at_ratio_between(
&self,
start: Point<F>,
end: Point<F>,
ratio_from_start: F,
) -> Point<F> {
let calculations = RhumbCalculations::new(&start, &end);
calculations.intermediate(ratio_from_start)
}
fn points_along_line(
&self,
start: Point<F>,
end: Point<F>,
max_distance: F,
include_ends: bool,
) -> impl Iterator<Item = Point<F>> {
let max_delta = max_distance / F::from(MEAN_EARTH_RADIUS).unwrap();
let calculations = RhumbCalculations::new(&start, &end);
calculations
.intermediate_fill(max_delta, include_ends)
.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod bearing {
use super::*;
#[test]
fn north() {
let origin = Point::new(0.0, 0.0);
let destination = Point::new(0.0, 1.0);
assert_relative_eq!(0.0, Rhumb.bearing(origin, destination));
}
#[test]
fn east() {
let origin = Point::new(0.0, 0.0);
let destination = Point::new(1.0, 0.0);
assert_relative_eq!(90.0, Rhumb.bearing(origin, destination));
}
#[test]
fn south() {
let origin = Point::new(0.0, 0.0);
let destination = Point::new(0.0, -1.0);
assert_relative_eq!(180.0, Rhumb.bearing(origin, destination));
}
#[test]
fn west() {
let origin = Point::new(0.0, 0.0);
let destination = Point::new(-1.0, 0.0);
assert_relative_eq!(270.0, Rhumb.bearing(origin, destination));
}
}
mod destination {
use super::*;
#[test]
fn north() {
let origin = Point::new(0.0, 0.0);
let bearing = 0.0;
assert_relative_eq!(
Point::new(0.0, 0.899320363724538),
Rhumb.destination(origin, bearing, 100_000.0)
);
}
#[test]
fn east() {
let origin = Point::new(0.0, 0.0);
let bearing = 90.0;
assert_relative_eq!(
Point::new(0.8993203637245415, 5.506522912913066e-17),
Rhumb.destination(origin, bearing, 100_000.0)
);
}
#[test]
fn south() {
let origin = Point::new(0.0, 0.0);
let bearing = 180.0;
assert_relative_eq!(
Point::new(0.0, -0.899320363724538),
Rhumb.destination(origin, bearing, 100_000.0)
);
}
#[test]
fn west() {
let origin = Point::new(0.0, 0.0);
let bearing = 270.0;
assert_relative_eq!(
Point::new(-0.8993203637245415, -1.6520247072649334e-16),
Rhumb.destination(origin, bearing, 100_000.0)
);
}
}
mod distance {
use super::*;
#[test]
fn new_york_to_london() {
let new_york_city = Point::new(-74.006, 40.7128);
let london = Point::new(-0.1278, 51.5074);
let distance: f64 = Rhumb.distance(new_york_city, london);
assert_relative_eq!(
5_794_129., distance.round()
);
}
}
mod interpolate_point {
use super::*;
#[test]
fn point_at_ratio_between_midpoint() {
let start = Point::new(10.0, 20.0);
let end = Point::new(125.0, 25.0);
let midpoint = Rhumb.point_at_ratio_between(start, end, 0.5);
assert_relative_eq!(
midpoint,
Point::new(66.98011173721943, 22.500000000000007),
epsilon = 1.0e-10
);
}
#[test]
fn points_along_line_with_endpoints() {
let start = Point::new(10.0, 20.0);
let end = Point::new(125.0, 25.0);
let max_dist = 1000000.0; let route = Rhumb
.points_along_line(start, end, max_dist, true)
.collect::<Vec<_>>();
assert_eq!(route.len(), 13);
assert_eq!(route[0], start);
assert_eq!(route.last().unwrap(), &end);
assert_relative_eq!(
route[1],
Point::new(19.43061818495096, 20.416666666666668),
epsilon = 1.0e-10
);
}
#[test]
fn points_along_line_without_endpoints() {
let start = Point::new(10.0, 20.0);
let end = Point::new(125.0, 25.0);
let max_dist = 1000000.0; let route = Rhumb
.points_along_line(start, end, max_dist, false)
.collect::<Vec<_>>();
assert_eq!(route.len(), 11);
assert_relative_eq!(
route[0],
Point::new(19.43061818495096, 20.416666666666668),
epsilon = 1.0e-10
);
}
}
}