1use std::fmt::Display;
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq, bincode::Decode, bincode::Encode)]
28pub struct FPCoordinate {
29 pub lat: i32,
30 pub lon: i32,
31}
32
33impl FPCoordinate {
34 pub const fn new(lat: i32, lon: i32) -> Self {
35 Self { lat, lon }
36 }
37
38 pub const fn min() -> Self {
39 Self {
40 lat: i32::MIN,
41 lon: i32::MIN,
42 }
43 }
44
45 pub const fn max() -> Self {
46 Self {
47 lat: i32::MAX,
48 lon: i32::MAX,
49 }
50 }
51
52 pub fn new_from_lat_lon(lat: f64, lon: f64) -> Self {
53 let lat = (lat * 1000000.) as i32;
54 let lon = (lon * 1000000.) as i32;
55
56 Self { lat, lon }
57 }
58
59 pub fn to_lon_lat_pair(&self) -> (f64, f64) {
60 (self.lon as f64 / 1000000., self.lat as f64 / 1000000.)
61 }
62
63 pub fn distance(first: &FPCoordinate, second: &FPCoordinate) -> f64 {
64 let (lona, lata) = first.to_lon_lat_pair();
65 let (lonb, latb) = second.to_lon_lat_pair();
66 crate::great_circle::haversine(lata, lona, latb, lonb)
67 }
68 pub fn to_lon_lat_vec(&self) -> Vec<f64> {
69 let (lon, lat) = self.to_lon_lat_pair();
70 vec![lon, lat]
71 }
72
73 pub fn distance_to(&self, other: &FPCoordinate) -> f64 {
93 distance(self, other)
94 }
95}
96
97#[derive(Clone, Copy, Debug, PartialEq)]
98pub struct Point2D {
99 pub x: f64,
100 pub y: f64,
101}
102
103impl Point2D {
104 pub fn new() -> Self {
105 Point2D { x: 0., y: 0. }
106 }
107}
108
109impl Default for Point2D {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115pub struct Segment(pub Point2D, pub Point2D);
116
117pub fn distance_to_segment_2d(point: &Point2D, segment: &Segment) -> (f64, Point2D) {
118 let mut dx = segment.1.x - segment.0.x;
119 let mut dy = segment.1.y - segment.0.y;
120
121 if (dx == 0.) && (dy == 0.) {
122 dx = point.x - segment.0.x;
124 dy = point.y - segment.0.y;
125 return ((dx * dx + dy * dy).sqrt(), segment.0);
126 }
127
128 let t = ((point.x - segment.0.x) * dx + (point.y - segment.0.y) * dy) / (dx * dx + dy * dy);
130
131 let closest;
134 if t < 0. {
135 closest = segment.0;
136 dx = point.x - segment.0.x;
137 dy = point.y - segment.0.y;
138 } else if t > 1. {
139 closest = Point2D {
140 x: segment.1.x,
141 y: segment.1.y,
142 };
143 dx = point.x - segment.1.x;
144 dy = point.y - segment.1.y;
145 } else {
146 closest = Point2D {
147 x: segment.0.x + t * dx,
148 y: segment.0.y + t * dy,
149 };
150 dx = point.x - closest.x;
151 dy = point.y - closest.y;
152 }
153
154 ((dx * dx + dy * dy).sqrt(), closest)
155}
156
157impl Display for FPCoordinate {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(
160 f,
161 "{:.6}, {:.6}",
162 self.lat as f64 / 1000000.,
163 self.lon as f64 / 1000000.
164 )
165 }
166}
167
168pub const fn cross_product(o: &FPCoordinate, a: &FPCoordinate, b: &FPCoordinate) -> i64 {
169 let first = (a.lon as i64 - o.lon as i64) * (b.lat as i64 - o.lat as i64);
171 let second = (a.lat as i64 - o.lat as i64) * (b.lon as i64 - o.lon as i64);
172 first - second
173}
174
175pub const fn is_clock_wise_turn(o: &FPCoordinate, a: &FPCoordinate, b: &FPCoordinate) -> bool {
176 let first = (a.lon as i64 - o.lon as i64) * (b.lat as i64 - o.lat as i64);
178 let second = (a.lat as i64 - o.lat as i64) * (b.lon as i64 - o.lon as i64);
179 first > second
180}
181
182pub fn distance(first: &FPCoordinate, b: &FPCoordinate) -> f64 {
183 let (lona, lata) = first.to_lon_lat_pair();
184 let (lonb, latb) = b.to_lon_lat_pair();
185 crate::great_circle::haversine(lata, lona, latb, lonb)
186}
187
188#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub struct IPoint2D {
192 pub x: i32,
193 pub y: i32,
194}
195
196impl IPoint2D {
197 pub fn new(x: i32, y: i32) -> Self {
198 Self { x, y }
199 }
200
201 pub fn distance_to(&self, other: &IPoint2D) -> i32 {
206 let xd = self.x - other.x;
207 let yd = self.y - other.y;
208 let d = ((xd * xd + yd * yd) as f64).sqrt();
209 d.round() as i32
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 macro_rules! assert_delta {
216 ($x:expr, $y:expr, $d:expr) => {
217 if !($x - $y < $d || $y - $x < $d) {
218 panic!();
219 }
220 };
221 }
222
223 use super::{FPCoordinate, cross_product, distance};
224 use crate::geometry::{IPoint2D, Point2D, Segment, distance_to_segment_2d, is_clock_wise_turn};
225
226 #[test]
227 fn distance_one() {
228 let p = Point2D { x: 1., y: 2. };
229 let s = Segment(Point2D { x: 0., y: 0. }, Point2D { x: 0., y: 10. });
230 let (distance, location) = distance_to_segment_2d(&p, &s);
231 assert_eq!(distance, 1.);
232 assert_eq!(location, Point2D { x: 0., y: 2. });
233 }
234
235 #[test]
236 fn distance_on_line() {
237 let p = Point2D { x: 1., y: 2. };
238 let s = Segment(Point2D { x: 0., y: 0. }, Point2D { x: 0., y: 0. });
239 let (distance, location) = distance_to_segment_2d(&p, &s);
240 assert_eq!(distance, 2.23606797749979);
241 assert_eq!(location, Point2D { x: 0., y: 0. });
242 }
243
244 #[test]
245 fn distance_to_zero_length_segment() {
246 let point = Point2D { x: 3.0, y: 4.0 };
247 let segment_point = Point2D { x: 0.0, y: 0.0 };
248 let segment = Segment(segment_point, segment_point); let (distance, closest) = distance_to_segment_2d(&point, &segment);
251
252 assert_eq!(distance, 5.0);
254 assert_eq!(closest, segment_point);
256 }
257
258 #[test]
259 fn distance_beyond_segment_end() {
260 let point = Point2D { x: 0.0, y: 3.0 };
261 let segment = Segment(Point2D { x: 0.0, y: 0.0 }, Point2D { x: 0.0, y: 2.0 });
262
263 let (distance, closest) = distance_to_segment_2d(&point, &segment);
264
265 assert_eq!(distance, 1.0);
267 assert_eq!(closest, segment.1);
269 }
270
271 #[test]
272 fn distance_beyond_diagonal_segment_end() {
273 let point = Point2D { x: 3.0, y: 3.0 };
274 let segment = Segment(Point2D { x: 0.0, y: 0.0 }, Point2D { x: 2.0, y: 2.0 });
275
276 let (distance, closest) = distance_to_segment_2d(&point, &segment);
277
278 assert_delta!(distance, 2_f64.sqrt(), 0.000001);
280 assert_eq!(closest, segment.1);
282 }
283
284 #[test]
285 fn distance_before_segment_start() {
286 let point = Point2D { x: -1.0, y: -1.0 };
287 let segment = Segment(Point2D { x: 0.0, y: 0.0 }, Point2D { x: 2.0, y: 2.0 });
288
289 let (distance, closest) = distance_to_segment_2d(&point, &segment);
290
291 assert_delta!(distance, 2_f64.sqrt(), 0.000001);
293 assert_eq!(closest, segment.0);
295 }
296
297 #[test]
298 fn cross_product_ex1() {
299 let o = FPCoordinate::new(1, 1);
300 let a = FPCoordinate::new(4, 5);
301 let b = FPCoordinate::new(5, 4);
302 assert_eq!(7, cross_product(&o, &a, &b))
303 }
304
305 #[test]
306 fn cross_product_ex2() {
307 let o = FPCoordinate::new(0, 0);
308 let a = FPCoordinate::new(7, 5);
309 let b = FPCoordinate::new(17, 13);
310 assert_eq!(-6, cross_product(&o, &a, &b))
311 }
312
313 #[test]
314 fn cross_product_ex3() {
315 let o = FPCoordinate::new(0, 0);
316 let a = FPCoordinate::new(2, 2);
317 let b = FPCoordinate::new(0, -3);
318 assert_eq!(6, cross_product(&o, &a, &b))
319 }
320
321 #[test]
322 fn clock_wise_turn() {
323 let o = FPCoordinate::new_from_lat_lon(33.376756, -114.990162);
324 let a = FPCoordinate::new_from_lat_lon(33.359699, -114.945064);
325 let b = FPCoordinate::new_from_lat_lon(33.412820, -114.943641);
326
327 let cp = cross_product(&o, &a, &b);
328 assert!(cp > 0);
329 assert!(is_clock_wise_turn(&o, &a, &b));
330 }
331
332 #[test]
333 fn println() {
334 let input = FPCoordinate::new_from_lat_lon(33.359699, -114.945064);
335 let output = format!("{input}");
336 assert_eq!(output, "33.359699, -114.945064");
337 }
338
339 #[test]
340 fn println_truncated() {
341 let input = FPCoordinate::new_from_lat_lon(33.359_699_123_456_79, -114.945064127454);
342 let output = format!("{input}");
343 assert_eq!(output, "33.359699, -114.945064");
344 }
345
346 #[test]
347 fn to_lon_lat_pair_vec_equivalent() {
348 let input = FPCoordinate::new_from_lat_lon(33.359_699_123_456_79, -114.945064127454);
349 let output1 = input.to_lon_lat_pair();
350 let output2 = input.to_lon_lat_vec();
351
352 assert_eq!(output1.0, output2[0]);
353 assert_eq!(output1.1, output2[1]);
354 }
355
356 #[test]
357 fn trivial_distance_equivalent() {
358 let ny = FPCoordinate::new_from_lat_lon(40.730610, -73.935242);
359 let sf = FPCoordinate::new_from_lat_lon(37.773972, -122.431297);
360 let distance = distance(&ny, &sf);
361
362 assert_delta!(distance, 4140.175105689902, 0.0000001);
363 }
364
365 #[test]
366 fn point_self_new_equivalent() {
367 let p1 = Point2D::default();
368 let p2 = Point2D::new();
369 assert_eq!(p1, p2);
370 }
371
372 #[test]
373 fn test_distance_to() {
374 let ny = FPCoordinate::new_from_lat_lon(40.730610, -73.935242); let sf = FPCoordinate::new_from_lat_lon(37.773972, -122.431297); let d1 = ny.distance_to(&sf);
379 let d2 = sf.distance_to(&ny);
380 assert_eq!(d1, d2);
381
382 assert_delta!(d1, 4140.175105689902, 0.0000001);
384
385 assert_eq!(ny.distance_to(&ny), 0.0);
387 }
388
389 #[test]
390 fn test_coordinate_encoding() {
391 let original = FPCoordinate::new_from_lat_lon(40.730610, -73.935242);
392
393 let encoded: Vec<u8> =
395 bincode::encode_to_vec(original, bincode::config::standard()).unwrap();
396
397 let decoded: FPCoordinate =
399 bincode::decode_from_slice(&encoded, bincode::config::standard())
400 .unwrap()
401 .0;
402
403 assert_eq!(original, decoded);
405 assert_eq!(original.lat, decoded.lat);
406 assert_eq!(original.lon, decoded.lon);
407
408 let (lon, lat) = decoded.to_lon_lat_pair();
410 assert_delta!(lat, 40.730610, 0.000001);
411 assert_delta!(lon, -73.935242, 0.000001);
412 }
413
414 #[test]
415 fn test_ipoint2d_distance() {
416 let cases = [
418 ((0, 0), (3, 0), 3), ((0, 0), (0, 4), 4), ((0, 0), (3, 4), 5), ((1, 1), (4, 5), 5), ((2, 2), (5, 6), 5), ];
424
425 for ((x1, y1), (x2, y2), expected) in cases {
426 let p1 = IPoint2D::new(x1, y1);
427 let p2 = IPoint2D::new(x2, y2);
428 assert_eq!(p1.distance_to(&p2), expected);
429 assert_eq!(p2.distance_to(&p1), expected);
431 }
432 }
433}