bambam_osm/algorithm/
connected_components.rs1use crate::model::osm::{
2 graph::{OsmGraph, OsmNodeId},
3 OsmError,
4};
5use itertools::Itertools;
6use kdam::{tqdm, Bar, BarExt};
7use std::collections::{HashMap, HashSet, VecDeque};
8
9pub type UndirectedAdjacencyList = HashMap<OsmNodeId, HashSet<OsmNodeId>>;
10
11pub fn to_undirected(graph: &OsmGraph) -> UndirectedAdjacencyList {
12 let mut undirected: HashMap<OsmNodeId, HashSet<OsmNodeId>> = HashMap::new();
13 let adjacencies_iter = tqdm!(
14 graph.connected_node_pair_iterator(false),
15 desc = "create undirected graph"
16 );
17 for (src, dst) in adjacencies_iter {
18 add_undirected_edge(src, dst, &mut undirected);
19 }
20
21 eprintln!();
22 undirected
23}
24
25fn add_undirected_edge(src: &OsmNodeId, dst: &OsmNodeId, g: &mut UndirectedAdjacencyList) {
28 if src == dst {
29 return;
30 }
31 g.entry(*src)
32 .and_modify(|h| {
33 h.insert(*dst);
34 })
35 .or_insert(HashSet::from([*dst]));
36 g.entry(*dst)
37 .and_modify(|h| {
38 h.insert(*src);
39 })
40 .or_insert(HashSet::from([*src]));
41}
42
43pub fn weakly_connected_components(
56 graph: &OsmGraph,
57 nodes: &[OsmNodeId],
58) -> Result<Vec<Vec<OsmNodeId>>, OsmError> {
59 let undirected = to_undirected(graph);
60
61 let n_unassigned = nodes.len();
62 let mut assigned: HashSet<OsmNodeId> = HashSet::new();
63 let mut solution: Vec<Vec<OsmNodeId>> = vec![];
64 let mut bar = Bar::builder()
65 .total(n_unassigned)
66 .desc("weakly connected components search")
67 .build()
68 .map_err(OsmError::InternalError)?;
69
70 for node_id in nodes.iter() {
72 if !assigned.contains(node_id) {
73 let cluster = bfs_undirected(node_id, &undirected)?;
74 for cluster_node_id in cluster.iter() {
75 assigned.insert(*cluster_node_id);
76 }
77 solution.push(cluster);
78 }
79 let _ = bar.update(1);
80 }
81 eprintln!();
82 log::info!("found {} weakly-connected components", solution.len());
83 Ok(solution)
84}
85
86pub fn bfs_undirected(
89 source: &OsmNodeId,
90 graph: &UndirectedAdjacencyList,
91) -> Result<Vec<OsmNodeId>, OsmError> {
92 let mut visited: HashSet<OsmNodeId> = HashSet::from([*source]);
94
95 let mut frontier: VecDeque<OsmNodeId> = VecDeque::new();
97 for n in graph.get(source).into_iter().flatten() {
98 frontier.push_back(*n);
99 }
100
101 let mut bar = Bar::builder()
102 .desc("connected components")
103 .build()
104 .map_err(|e| OsmError::InternalError(e.to_string()))?;
105
106 while let Some(next_id) = frontier.pop_front() {
108 if !visited.contains(&next_id) {
109 visited.insert(next_id);
110 if visited.len() == graph.len() {
111 return Ok(visited.into_iter().collect_vec());
113 }
114 for n in graph.get(&next_id).into_iter().flatten() {
115 if !visited.contains(n) {
116 frontier.push_back(*n);
117 }
118 }
119 }
120
121 let _ = bar.update(1);
122 }
123
124 Ok(visited.into_iter().collect_vec())
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::model::osm::graph::{OsmGraph, OsmNodeData, OsmNodeId, OsmWayData, OsmWayId};
130 use std::collections::HashMap;
131
132 #[test]
133 fn test_bfs_circle_with_dot() {
134 let source = OsmNodeId(0);
135 let n_connected_nodes: usize = 20;
136
137 let mut nodes = create_nodes_in_circle(n_connected_nodes);
138 nodes.insert(OsmNodeId(999), OsmNodeData::default());
140
141 let mut ways: HashMap<OsmWayId, OsmWayData> = HashMap::new();
142 let n_iters = (n_connected_nodes - 1) as i64;
143 for i in 0..n_iters {
144 let reversed = i % 2 == 0;
145 let src = if reversed {
146 OsmNodeId(i + 1)
147 } else {
148 OsmNodeId(i)
149 };
150 let dst = if reversed {
151 OsmNodeId(i)
152 } else {
153 OsmNodeId(i + 1)
154 };
155 let way_id = OsmWayId(i);
156 let mut way = OsmWayData::default();
157 way.osmid = way_id;
158 way.nodes = vec![src, dst];
159
160 ways.insert(way.osmid, way);
161 }
162
163 let graph = OsmGraph::new(nodes, ways).unwrap();
165 let undirected_graph = super::to_undirected(&graph);
166 let result = super::bfs_undirected(&source, &undirected_graph).unwrap();
167 assert_eq!(result.len(), n_connected_nodes);
168 }
169
170 #[test]
171 fn test_bfs_complete_graph_5() {
172 let source = OsmNodeId(0);
173 let n_connected_nodes: usize = 5;
174 let nodes = create_nodes_in_circle(n_connected_nodes);
175 let mut ways: HashMap<OsmWayId, OsmWayData> = HashMap::new();
176
177 let n_iters = n_connected_nodes as i64;
178 let mut way_id = 0;
179 for i in 0..n_iters {
180 let src = OsmNodeId(i);
181 for j in 0..n_iters {
182 if i == j {
183 continue;
184 }
185 let dst = OsmNodeId(j);
186 let this_way_id = OsmWayId(way_id);
187 let mut way = OsmWayData::default();
188 way.osmid = this_way_id;
189 way.nodes = vec![src, dst];
190 ways.insert(way.osmid, way);
191 way_id += 1;
192 }
193 }
194 let graph = OsmGraph::new(nodes, ways).unwrap();
195 let undirected_graph = super::to_undirected(&graph);
196
197 eprintln!(
198 "{}",
199 serde_json::to_string_pretty(&undirected_graph).unwrap()
200 );
201
202 let result = super::bfs_undirected(&source, &undirected_graph).unwrap();
203 assert_eq!(result.len(), n_connected_nodes);
204 eprintln!("{result:?}");
205 }
206
207 fn create_nodes_in_circle(n_connected_nodes: usize) -> HashMap<OsmNodeId, OsmNodeData> {
209 (0..n_connected_nodes)
210 .map(|n| {
211 let node_id = OsmNodeId(n.try_into().unwrap());
212 let mut node = OsmNodeData::default();
213 node.osmid = node_id.clone();
214 let angle = 2.0 * std::f32::consts::PI * (n as f32) / (n_connected_nodes as f32);
215 let x = angle.cos();
216 let y = angle.sin();
217 node.x = x;
218 node.y = y;
219 (node_id, node)
220 })
221 .collect()
222 }
223}