geo_core/
point_utils.rs

1use crate::{
2    constants::EARTH_RADIUS, latlng::LatLng, point::Point, rectangle::Rectangle, utils::deg_to_rad,
3};
4
5//PointToPointDistanceCosine Get distance (in meters) between two Lat/Lons using cosine formula. Has some benefits over using Haversine formula.
6//Input and Output in Radians
7pub fn point_to_point_distance_cosine(start_point: Point, end_point: Point) -> f64 {
8    (start_point.y.sin() * end_point.y.sin()
9        + start_point.y.cos() * end_point.y.cos() * (start_point.x - end_point.x).cos())
10    .acos()
11        * EARTH_RADIUS
12}
13
14// //PointToPointHaversine - Alternative method for getting distance (in meters) bewteen two Lat/Longs using cosine formula. Possibly slightly faster.
15pub fn point_to_point_haversine(start: LatLng, end: LatLng) -> f64 {
16    let start_radian = start.convert_to_radian();
17    let end_radian = end.convert_to_radian();
18    let dif_lat = end_radian.lat - start_radian.lat;
19    let dif_lng = end_radian.lng - start_radian.lng;
20
21    let a = (dif_lat / 2.0).sin() * (dif_lat / 2.0).sin()
22        + start_radian.lat.cos()
23            * end_radian.lat.cos()
24            * (dif_lng / 2.0).sin()
25            * (dif_lng / 2.0).sin();
26    let c = 2.0 * (a.sqrt().atan2((1.0 - a).sqrt()));
27    let d = EARTH_RADIUS * c;
28    return d;
29}
30
31//PointToPointCartesianDistance - Get distance betweeen two cartesian points
32pub fn point_to_point_cartesian_distance(source: Point, target: Point) -> f64 {
33    ((target.x - source.x).powi(2) + (target.y - source.y).powi(2)).sqrt()
34}
35
36//https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
37//PointToRectangleDistance - Get distance to closet point on a rectangle
38//Cartesian coordinates.
39pub fn point_to_rectangle_distance(p: Point, r: Rectangle) -> f64 {
40    let pre_max_x = (r.min_point.x - p.x).max(0.0);
41    let dx = (pre_max_x).max(p.x - r.max_point.x);
42    let pre_max_y = (r.min_point.y - p.y).max(0.0);
43    let dy = (pre_max_y).max(p.y - r.max_point.y);
44    return (dx * dx + dy * dy).sqrt();
45}
46
47//PointToRectangleDistance - Get distance to closet point on a rectangle/boudning box
48// Geographic
49// TODO Confirm this is working
50pub fn point_to_rectangle_distance_geographic(l: LatLng, b: Rectangle) -> f64 {
51    let converted = l.convert_to_radian();
52    let c_as_point = converted.convert_to_point();
53
54    if check_between_longs(&l, &b) {
55        //If between Lngs then:
56        //Either straight down or straight up to bounding box.
57        if l.lat < b.min_point.y {
58            return point_to_point_distance_cosine(
59                c_as_point,
60                Point {
61                    x: c_as_point.x,
62                    y: deg_to_rad(b.min_point.y),
63                },
64            );
65        }
66        if l.lat > b.max_point.y {
67            return point_to_point_distance_cosine(
68                c_as_point,
69                Point {
70                    x: c_as_point.x,
71                    y: deg_to_rad(b.max_point.y),
72                },
73            );
74        }
75        //Actually inside so return 0 distance
76        return 0.0;
77    }
78
79    //If on West
80    if l.lng < b.min_point.x {
81        //If North West, Use North West Corner
82        if l.lat > b.max_point.y {
83            return point_to_point_distance_cosine(
84                c_as_point,
85                Point {
86                    x: deg_to_rad(b.min_point.x),
87                    y: deg_to_rad(b.max_point.y),
88                },
89            );
90        }
91        //If South West, use South West Corner
92        if l.lat < b.min_point.y {
93            return point_to_point_distance_cosine(
94                c_as_point,
95                Point {
96                    x: deg_to_rad(b.min_point.x),
97                    y: deg_to_rad(b.min_point.y),
98                },
99            );
100        }
101        //Else Use line
102        return point_to_point_distance_cosine(
103            c_as_point,
104            Point {
105                x: deg_to_rad(b.min_point.x),
106                y: c_as_point.y,
107            },
108        );
109    }
110
111    //must be on east
112    //If North East, Use North East Corner
113    if l.lat > b.max_point.y {
114        return point_to_point_distance_cosine(
115            c_as_point,
116            Point {
117                x: deg_to_rad(b.max_point.x),
118                y: deg_to_rad(b.max_point.y),
119            },
120        );
121    }
122    //If South East, use South East Corner
123    if l.lat < b.min_point.y {
124        return point_to_point_distance_cosine(
125            c_as_point,
126            Point {
127                x: deg_to_rad(b.max_point.x),
128                y: deg_to_rad(b.min_point.y),
129            },
130        );
131    }
132    //Else Use line
133    return point_to_point_distance_cosine(
134        c_as_point,
135        Point {
136            x: deg_to_rad(b.max_point.x),
137            y: c_as_point.y,
138        },
139    );
140}
141
142fn check_between_longs(l: &LatLng, b: &Rectangle) -> bool {
143    if l.lng >= b.min_point.x && l.lng <= b.max_point.x {
144        return true;
145    }
146    return false;
147}
148
149// MidPointGeographicCoordinates - Get the mid point of two points (geographic)
150// Input and Output in Radians.
151pub fn mid_point_geographic_coordinates(a: LatLng, b: LatLng) -> LatLng {
152    let bx = b.lat.cos() * (b.lng - a.lng).cos();
153    let by = b.lat.cos() * (b.lng - a.lng).sin();
154    let lng_mid = a.lng + by.atan2(a.lat.cos() + bx); // ?? Check
155    let lat_mid = (a.lat.sin() + b.lat.sin())
156        .atan2(((a.lat.cos() + bx) * (a.lat.cos() + bx) + by * by).sqrt());
157
158    LatLng {
159        lng: lng_mid,
160        lat: lat_mid,
161    }
162}
163
164// FindBearing - Returns bearing between a start and end point
165// Input and Output in Radians.
166pub fn find_bearing(start_point: Point, end_point: Point) -> f64 {
167    let y = (end_point.x - start_point.x).sin() * end_point.y.cos();
168    let x = start_point.y.cos() * end_point.y.sin()
169        - start_point.y.sin() * end_point.y.cos() * (end_point.x - start_point.x).cos();
170    y.atan2(x)
171}
172
173#[cfg(test)]
174mod tests {
175    use crate::utils::deg_to_rad;
176
177    use super::*;
178
179    #[test]
180    fn test_point_to_point_distance_cosine() {
181        let start_lat = 51.27936;
182        let start_lng = 1.07263;
183        let end_lat = 51.27741;
184        let end_lng = 1.07281;
185
186        let start_point = Point {
187            x: deg_to_rad(start_lng),
188            y: deg_to_rad(start_lat),
189        };
190
191        let end_point = Point {
192            x: deg_to_rad(end_lng),
193            y: deg_to_rad(end_lat),
194        };
195
196        let distance = point_to_point_distance_cosine(start_point, end_point);
197
198        assert_eq!(distance.round(), 217.0);
199    }
200    #[test]
201    fn test_point_to_point_distance_haversine() {
202        let start_lat = 51.27936;
203        let start_lng = 1.07263;
204        let end_lat = 51.27741;
205        let end_lng = 1.07281;
206
207        let start_lat_lng = LatLng {
208            lat: start_lat,
209            lng: start_lng,
210        };
211
212        let end_lat_lng = LatLng {
213            lat: end_lat,
214            lng: end_lng,
215        };
216
217        let distance = point_to_point_haversine(start_lat_lng, end_lat_lng);
218
219        assert_eq!(distance.round(), 217.0);
220    }
221
222    #[test]
223    fn test_point_to_bounding_box_geographic() {
224        let test_bbox = Rectangle {
225            min_point: Point {
226                x: 1.070678,
227                y: 51.279336,
228            },
229            max_point: Point {
230                x: 1.071720,
231                y: 51.277559,
232            },
233        };
234
235        // //Point which should return a distnace to a point on the line.
236        let start_point = LatLng {
237            lat: 51.27936,
238            lng: 1.07263,
239        };
240        let distance = point_to_rectangle_distance_geographic(start_point, test_bbox);
241
242        assert_eq!(distance.round(), 210.0);
243    }
244}