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