1use crate::graph::{self, SpatialGraph};
2use crate::overpass;
3use crate::cache;
4use crate::error::OsmGraphError;
5use crate::overpass::NetworkType;
6
7use geo::{ConcaveHull, ConvexHull, KNearestConcaveHull, MultiPoint, Polygon};
8use petgraph::algo::dijkstra;
9use petgraph::prelude::*;
10use std::collections::HashSet;
11use std::sync::Arc;
12
13#[derive(Debug, Copy, Clone)]
14pub enum HullType {
15 FastConcave,
16 Concave,
17 Convex,
18}
19
20pub fn calculate_isochrones(
21 graph: &DiGraph<graph::XmlNode, graph::XmlWay>,
22 start_node: NodeIndex,
23 time_limits: Vec<f64>,
24 hull_type: HullType,
25) -> Vec<Polygon> {
26 let mut isochrones = Vec::new();
27
28 let shortest_paths = dijkstra(graph, start_node, None, |e| {
30 let edge_weight = graph.edge_weight(e.id()).unwrap();
31 edge_weight.drive_travel_time
32 });
33
34 for &time_limit in &time_limits {
36 let isochrone_nodes = shortest_paths
37 .iter()
38 .filter_map(|(&node, &time)| if time <= time_limit { Some(node) } else { None })
39 .collect::<HashSet<_>>();
40
41 let isochrone_lat_lons = isochrone_nodes
43 .into_iter()
44 .map(|node_index| graph::node_to_latlon(graph, node_index))
45 .collect::<Vec<_>>();
46
47 let points: MultiPoint<f64> = isochrone_lat_lons.into();
48
49 let hull = match hull_type {
50 HullType::FastConcave => points.concave_hull(2.0),
51 HullType::Concave => points.k_nearest_concave_hull(3),
52 HullType::Convex => points.convex_hull(),
53 };
54
55 isochrones.push(hull);
56 }
57
58 isochrones
59}
60
61pub fn calculate_isochrones_concurrently(
62 graph: std::sync::Arc<DiGraph<graph::XmlNode, graph::XmlWay>>,
63 start_node: NodeIndex,
64 time_limits: Vec<f64>,
65 network_type: overpass::NetworkType,
66 hull_type: HullType,
67) -> Vec<Polygon> {
68 let shortest_paths = dijkstra(&*graph, start_node, None, |e| {
70 let way = graph.edge_weight(e.id()).unwrap();
71 match network_type {
72 NetworkType::Walk => way.walk_travel_time,
73 NetworkType::Bike => way.bike_travel_time,
74 _ => way.drive_travel_time,
75 }
76 });
77
78 let node_times: Vec<(NodeIndex, f64)> = shortest_paths.into_iter().collect();
80 let node_times = std::sync::Arc::new(node_times);
81
82 let mut handles = vec![];
83 for time_limit in time_limits {
84 let graph_clone = std::sync::Arc::clone(&graph);
85 let node_times_clone = std::sync::Arc::clone(&node_times);
86 let handle = std::thread::spawn(move || {
87 let points: MultiPoint<f64> = node_times_clone
88 .iter()
89 .filter(|(_, t)| *t <= time_limit)
90 .map(|(node, _)| graph::node_to_latlon(&*graph_clone, *node))
91 .collect::<Vec<_>>()
92 .into();
93
94 match hull_type {
95 HullType::FastConcave => points.concave_hull(2.0),
96 HullType::Concave => points.k_nearest_concave_hull(3),
97 HullType::Convex => points.convex_hull(),
98 }
99 });
100 handles.push(handle);
101 }
102
103 handles.into_iter().map(|h| h.join().unwrap()).collect()
104}
105
106
107pub async fn calculate_isochrones_from_point(
108 lat: f64,
109 lon: f64,
110 max_dist: Option<f64>,
111 time_limits: Vec<f64>,
112 network_type: overpass::NetworkType,
113 hull_type: HullType,
114 retain_all: bool,
115) -> Result<(Vec<Polygon>, SpatialGraph), OsmGraphError> {
116
117 let max_speed_m_per_s = match network_type {
121 NetworkType::Walk => 5.0 / 3.6,
122 NetworkType::Bike => 25.0 / 3.6,
123 _ => 120.0 / 3.6,
124 };
125 let max_time = time_limits.iter().cloned().fold(0.0_f64, f64::max);
126 let computed_dist = max_dist.unwrap_or_else(|| max_time * max_speed_m_per_s * 1.2);
127
128 let polygon_coord_str = overpass::bbox_from_point(lat, lon, computed_dist);
129 let query = overpass::create_overpass_query(&polygon_coord_str, network_type);
130 let graph_key = format!("{}:{}", query, retain_all);
131
132 let sg = if let Some(cached) = cache::check_cache(&graph_key)? {
133 cached
134 } else {
135 let xml = if let Some(cached_xml) = cache::check_xml_cache(&query)? {
136 cached_xml } else if let Some(disk_xml) = cache::check_disk_xml_cache(&query) {
138 cache::insert_into_xml_cache(query.clone(), disk_xml.clone())?; disk_xml } else {
141 let fetched = overpass::make_request("https://overpass-api.de/api/interpreter", &query).await?;
142 cache::write_disk_xml_cache(&query, &fetched); cache::insert_into_xml_cache(query.clone(), fetched.clone())?;
144 fetched };
146
147 let parsed = graph::parse_xml(&xml)?;
148 if parsed.nodes.is_empty() {
149 return Err(OsmGraphError::EmptyGraph);
150 }
151 let g = graph::create_graph(parsed.nodes, parsed.ways, retain_all, false);
152 let sg = SpatialGraph::new(g);
153 cache::insert_into_cache(graph_key, sg.clone())?;
154 sg
155 };
156
157 let node_index = sg.nearest_node(lat, lon).ok_or(OsmGraphError::NodeNotFound)?;
158 let shared_graph = Arc::clone(&sg.graph); let isochrones = calculate_isochrones_concurrently(
160 shared_graph,
161 node_index,
162 time_limits,
163 network_type,
164 hull_type,
165 );
166
167 Ok((isochrones, sg))
168}