geo/algorithm/rhumb/
mod.rs1use crate::{CoordFloat, Point, point, utils::normalize_longitude};
10use num_traits::FromPrimitive;
11
12mod distance;
13#[allow(deprecated)]
14pub use distance::RhumbDistance;
15
16mod bearing;
17#[allow(deprecated)]
18pub use bearing::RhumbBearing;
19
20mod destination;
21#[allow(deprecated)]
22pub use destination::RhumbDestination;
23
24mod intermediate;
25#[allow(deprecated)]
26pub use intermediate::RhumbIntermediate;
27
28mod length;
29#[allow(deprecated)]
30pub use length::RhumbLength;
31
32pub(crate) struct RhumbCalculations<T: CoordFloat + FromPrimitive> {
33 from: Point<T>,
34 to: Point<T>,
35 phi1: T,
36 delta_lambda: T,
37 delta_phi: T,
38 delta_psi: T,
39}
40
41impl<T: CoordFloat + FromPrimitive> RhumbCalculations<T> {
42 pub(crate) fn new(from: &Point<T>, to: &Point<T>) -> Self {
43 let pi = T::from(std::f64::consts::PI).unwrap();
44 let two = T::one() + T::one();
45 let four = two + two;
46
47 let phi1 = from.y().to_radians();
48 let phi2 = to.y().to_radians();
49 let mut delta_lambda = (to.x() - from.x()).to_radians();
50 if delta_lambda > pi {
52 delta_lambda = delta_lambda - (two * pi);
53 }
54 if delta_lambda < -pi {
55 delta_lambda = delta_lambda + (two * pi);
56 }
57
58 let delta_psi = ((phi2 / two + pi / four).tan() / (phi1 / two + pi / four).tan()).ln();
59 let delta_phi = phi2 - phi1;
60
61 RhumbCalculations {
62 from: *from,
63 to: *to,
64 phi1,
65 delta_lambda,
66 delta_phi,
67 delta_psi,
68 }
69 }
70
71 pub(crate) fn theta(&self) -> T {
72 self.delta_lambda.atan2(self.delta_psi)
73 }
74
75 pub(crate) fn delta(&self) -> T {
76 let threshold = T::from(10.0e-12).unwrap();
77 let q = if self.delta_psi.abs() > threshold {
78 self.delta_phi / self.delta_psi
79 } else {
80 self.phi1.cos()
81 };
82
83 (self.delta_phi * self.delta_phi + q * q * self.delta_lambda * self.delta_lambda).sqrt()
84 }
85
86 pub(crate) fn intermediate(&self, fraction: T) -> Point<T> {
87 let delta = fraction * self.delta();
88 let theta = self.theta();
89 let lambda1 = self.from.x().to_radians();
90 calculate_destination(delta, lambda1, self.phi1, theta)
91 }
92
93 pub(crate) fn intermediate_fill(&self, max_delta: T, include_ends: bool) -> Vec<Point<T>> {
94 let theta = self.theta();
95 let lambda1 = self.from.x().to_radians();
96
97 let total_delta = self.delta();
98
99 if total_delta <= max_delta {
100 return if include_ends {
101 vec![self.from, self.to]
102 } else {
103 vec![]
104 };
105 }
106
107 let number_of_points = (total_delta / max_delta).ceil();
108 let interval = T::one() / number_of_points;
109
110 let mut current_step = interval;
111 let mut points = if include_ends {
112 vec![self.from]
113 } else {
114 vec![]
115 };
116
117 while current_step < T::one() {
118 let delta = total_delta * current_step;
119 let point = calculate_destination(delta, lambda1, self.phi1, theta);
120 points.push(point);
121 current_step = current_step + interval;
122 }
123
124 if include_ends {
125 points.push(self.to);
126 }
127
128 points
129 }
130}
131
132pub(crate) fn calculate_destination<T: CoordFloat + FromPrimitive>(
133 delta: T,
134 lambda1: T,
135 phi1: T,
136 theta: T,
137) -> Point<T> {
138 let pi = T::from(std::f64::consts::PI).unwrap();
139 let two = T::one() + T::one();
140 let four = two + two;
141 let threshold = T::from(10.0e-12).unwrap();
142
143 let delta_phi = delta * theta.cos();
144 let mut phi2 = phi1 + delta_phi;
145
146 if phi2.abs() > pi / two {
148 phi2 = if phi2 > T::zero() {
149 pi - phi2
150 } else {
151 -pi - phi2
152 };
153 }
154
155 let delta_psi = ((phi2 / two + pi / four).tan() / (phi1 / two + pi / four).tan()).ln();
156 let q = if delta_psi.abs() > threshold {
158 delta_phi / delta_psi
159 } else {
160 phi1.cos()
161 };
162
163 let delta_lambda = (delta * theta.sin()) / q;
164 let lambda2 = lambda1 + delta_lambda;
165
166 point! {
167 x: normalize_longitude(lambda2.to_degrees()),
168 y: phi2.to_degrees(),
169 }
170}