Skip to main content

pysochrone/
utils.rs

1use geo::Polygon;
2use geojson::{GeoJson, Geometry, Value};
3
4pub fn calculate_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
5    let radius_earth = 6371000.0; // Radius of the Earth in meters
6
7    let dlat = (lat2 - lat1).to_radians();
8    let dlon = (lon2 - lon1).to_radians();
9
10    let lat1 = lat1.to_radians();
11    let lat2 = lat2.to_radians();
12
13    let a = (dlat / 2.0).sin().powi(2) + (dlon / 2.0).sin().powi(2) * lat1.cos() * lat2.cos();
14    let c = 2.0 * a.sqrt().asin();
15
16    radius_earth * c // Distance in meters
17}
18
19pub fn calculate_travel_time(length: f64, speed_kph: f64) -> f64 {
20    let speed_m_per_s = speed_kph / 3.6;
21    length / speed_m_per_s // Returns time in seconds
22}
23
24pub fn polygon_to_geojson(polygon: &Polygon<f64>) -> GeoJson {
25    // node_to_latlon returns (lat, lon) tuples which geo stores as (x=lat, y=lon).
26    // GeoJSON spec requires [longitude, latitude], so coord.y = lon, coord.x = lat.
27    let exterior_coords = polygon
28        .exterior()
29        .0
30        .iter()
31        .map(|coord| vec![coord.y, coord.x]) // [lon, lat] per GeoJSON spec
32        .collect::<Vec<_>>();
33
34    let geojson_polygon = Geometry::new(Value::Polygon(vec![exterior_coords]));
35
36    GeoJson::Geometry(geojson_polygon)
37}
38
39// Convert polygon to GeoJSON string
40pub fn polygon_to_geojson_string(polygon: &Polygon<f64>) -> String {
41    let geojson = polygon_to_geojson(polygon);
42    geojson.to_string()
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_distance_same_point() {
51        let d = calculate_distance(48.0, 11.0, 48.0, 11.0);
52        assert_eq!(d, 0.0);
53    }
54
55    #[test]
56    fn test_distance_known_value() {
57        // Munich to roughly 1km north — should be close to 1000m
58        let d = calculate_distance(48.0, 11.0, 48.009, 11.0);
59        assert!((d - 1000.0).abs() < 10.0, "Expected ~1000m, got {}", d);
60    }
61
62    #[test]
63    fn test_distance_is_symmetric() {
64        let d1 = calculate_distance(48.0, 11.0, 52.0, 13.0);
65        let d2 = calculate_distance(52.0, 13.0, 48.0, 11.0);
66        assert!((d1 - d2).abs() < 1e-6);
67    }
68
69    #[test]
70    fn test_travel_time_basic() {
71        // 1000m at 36 kph = 100 seconds
72        let t = calculate_travel_time(1000.0, 36.0);
73        assert!((t - 100.0).abs() < 1e-6, "Expected 100s, got {}", t);
74    }
75
76    #[test]
77    fn test_travel_time_walking() {
78        // 500m at 5 kph = 360 seconds
79        let t = calculate_travel_time(500.0, 5.0);
80        assert!((t - 360.0).abs() < 1e-6);
81    }
82}