1const EQUATORIAL_RADIUS: f64 = 6378137.0;
3const FLATTENING: f64 = 1.0 / 298.257223563;
4const SQUARED_ECCENTRICITY: f64 = FLATTENING * (2.0 - FLATTENING);
5
6pub type LatLon = (f64, f64);
8
9#[derive(Clone)]
26pub struct PlaneProjection {
27 lon_scale: f64,
29 lat_scale: f64,
31}
32
33impl PlaneProjection {
34 pub fn new(latitude: f64) -> Self {
36 let cos_lat = (latitude as f32).to_radians().cos() as f64;
38
39 let w2 = 1.0 / (1.0 - SQUARED_ECCENTRICITY * (1.0 - cos_lat * cos_lat));
41 let w = w2.sqrt();
42 let lon_scale = (EQUATORIAL_RADIUS * w * cos_lat).to_radians(); let lat_scale = (EQUATORIAL_RADIUS * w * w2 * (1.0 - SQUARED_ECCENTRICITY)).to_radians(); Self {
46 lon_scale,
47 lat_scale,
48 }
49 }
50
51 #[inline(always)]
53 pub fn square_distance(&self, a: LatLon, b: LatLon) -> f64 {
54 let lat_dist = (a.0 - b.0) * self.lat_scale;
55 let lon_dist = lon_diff(a.1, b.1) * self.lon_scale;
56 lat_dist * lat_dist + lon_dist * lon_dist
57 }
58
59 #[inline(always)]
61 pub fn distance(&self, a: LatLon, b: LatLon) -> f64 {
62 self.square_distance(a, b).sqrt()
63 }
64
65 pub fn square_distance_to_segment(&self, point: LatLon, segment: (LatLon, LatLon)) -> f64 {
67 let mut point = (
69 (point.0 - segment.0.0) * self.lat_scale,
70 lon_diff(point.1, segment.0.1) * self.lon_scale,
71 );
72 let segment = (
73 (segment.1.0 - segment.0.0) * self.lat_scale,
74 lon_diff(segment.1.1, segment.0.1) * self.lon_scale,
75 );
76 if segment.0 != 0.0 || segment.1 != 0.0 {
77 let projection = (point.0 * segment.0 + point.1 * segment.1)
78 / (segment.0 * segment.0 + segment.1 * segment.1);
79 if projection > 1.0 {
80 point.0 -= segment.0;
81 point.1 -= segment.1;
82 } else if projection > 0.0 {
83 point.0 -= segment.0 * projection;
84 point.1 -= segment.1 * projection;
85 }
86 }
87 point.0 * point.0 + point.1 * point.1
88 }
89
90 #[inline(always)]
92 pub fn distance_to_segment(&self, point: LatLon, segment: (LatLon, LatLon)) -> f64 {
93 self.square_distance_to_segment(point, segment).sqrt()
94 }
95
96 #[inline(always)]
99 pub fn heading(&self, a: LatLon, b: LatLon) -> f32 {
100 let dx = ((a.0 - b.0) * self.lat_scale) as f32;
102 let dy = (lon_diff(b.1, a.1) * self.lon_scale) as f32;
103
104 180.0 - dy.atan2(dx).to_degrees()
106 }
107}
108
109#[inline(always)]
111fn lon_diff(a: f64, b: f64) -> f64 {
112 let mut lon_diff = a - b;
113 if lon_diff > 180.0 {
114 lon_diff -= 360.0;
115 } else if lon_diff < -180.0 {
116 lon_diff += 360.0;
117 }
118 lon_diff
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 const MALMO_C: LatLon = (55.60330902847681, 13.001973666557435);
126 const LUND_C: LatLon = (55.704141722528554, 13.191304107330561);
127 const STOCKHOLM_C: LatLon = (59.33036105663399, 18.058682977850953);
128
129 #[test]
130 fn lon_diff_test() {
131 assert_eq!(lon_diff(0.0, 0.0), 0.0);
132 assert_eq!(lon_diff(100.0, 0.0), 100.0);
133 assert_eq!(lon_diff(100.0, -100.0), -160.0);
134 assert_eq!(lon_diff(177.0, -177.0), -6.0);
135 assert_eq!(lon_diff(358.0, 0.0), -2.0);
136 assert_eq!(lon_diff(0.0, 358.0), 2.0);
137 assert_eq!(lon_diff(0.0, -180.0), 180.0);
138 assert_eq!(lon_diff(1.0, -180.0), -179.0);
139 assert_eq!(lon_diff(180.0, 0.0), 180.0);
140 assert_eq!(lon_diff(180.0, -1.0), -179.0);
141 assert_eq!(lon_diff(180.0, -180.0), 0.0);
142 }
143
144 #[test]
145 fn distance_test() {
146 let proj = PlaneProjection::new(55.65);
147 assert_eq!(proj.distance(MALMO_C, LUND_C).round() as u32, 16374);
148
149 let proj = PlaneProjection::new((MALMO_C.0 + STOCKHOLM_C.0) * 0.5);
152 assert_eq!(proj.distance(MALMO_C, STOCKHOLM_C).round() as u32, 514_168); let proj = PlaneProjection::new(MALMO_C.0);
154 assert_eq!(proj.distance(MALMO_C, STOCKHOLM_C).round() as u32, 523_230); let proj = PlaneProjection::new(STOCKHOLM_C.0);
156 assert_eq!(proj.distance(MALMO_C, STOCKHOLM_C).round() as u32, 505_217); }
158
159 #[test]
160 fn distance_to_segment_test() {
161 let proj = PlaneProjection::new(0.0);
162 assert_eq!(
163 proj.distance_to_segment((0.0, 0.0), ((0.0, 0.0), (0.0, 1.0))),
164 0.0
165 );
166 assert_eq!(
167 proj.distance_to_segment((0.0, 0.5), ((0.0, 0.0), (0.0, 1.0))),
168 0.0
169 );
170 assert_eq!(
171 proj.distance_to_segment((0.0, 1.0), ((0.0, 0.0), (0.0, 1.0))),
172 0.0
173 );
174 assert_eq!(
175 proj.distance_to_segment((0.0, 0.0), ((0.0, 1.0), (0.0, 2.0))),
176 proj.distance((0.0, 0.0), (0.0, 1.0))
177 );
178 assert_eq!(
179 proj.distance_to_segment((0.0, 4.5), ((0.0, 1.0), (0.0, 3.0))),
180 proj.distance((0.0, 4.5), (0.0, 3.0))
181 );
182
183 assert_eq!(
185 proj.distance_to_segment((1.0, 2.0), ((3.0, 3.0), (3.0, 3.0))),
186 proj.distance((1.0, 2.0), (3.0, 3.0))
187 );
188
189 let proj = PlaneProjection::new(55.65);
190 assert_eq!(
191 proj.distance_to_segment(MALMO_C, (LUND_C, LUND_C)) as u32,
192 16373
193 );
194
195 assert_eq!(
198 proj.distance_to_segment((55.67817981392954, 13.058789566271836), (MALMO_C, LUND_C))
199 as u32,
200 3615
201 );
202 }
203
204 #[test]
205 fn heading_test() {
206 let proj = PlaneProjection::new(55.65);
207 assert_eq!(proj.heading((55.70, 13.19), (55.80, 13.19)) as i32, 0);
208 assert_eq!(proj.heading((55.70, 13.19), (55.60, 13.19)) as i32, 180);
209 assert_eq!(proj.heading((55.70, 13.19), (55.70, 13.29)) as i32, 90);
210 assert_eq!(proj.heading((55.70, 13.19), (55.70, 13.09)) as i32, 270);
211
212 assert_eq!(proj.heading(MALMO_C, LUND_C,) as i32, 46);
213 assert_eq!(proj.heading(LUND_C, MALMO_C,) as i32, 180 + 46);
214 }
215}