use geohash::encode;
use petgraph::graph::{DiGraph, NodeIndex};
use petgraph::visit::EdgeRef;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::{HashMap, HashSet};
use crate::graph::{XmlNode, XmlTag, XmlWay};
static ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
pub fn simplify_graph(graph: &DiGraph<XmlNode, XmlWay>) -> DiGraph<XmlNode, XmlWay> {
let (consolidated_graph, _) = consolidate_intersections(graph, 9);
let mut simplified_graph = DiGraph::new();
let mut endpoints: HashSet<NodeIndex> = HashSet::new();
let mut index_map: HashMap<NodeIndex, NodeIndex> = HashMap::new();
for node in consolidated_graph.node_indices() {
if is_endpoint(&consolidated_graph, node) {
endpoints.insert(node);
let new_index = simplified_graph.add_node(consolidated_graph[node].clone());
index_map.insert(node, new_index);
}
}
let mut added_edges: HashSet<(NodeIndex, NodeIndex)> = HashSet::new();
for &endpoint in &endpoints {
for neighbor in consolidated_graph.neighbors_directed(endpoint, petgraph::Outgoing) {
if added_edges.contains(&(endpoint, neighbor)) {
continue;
}
let path = build_path(&consolidated_graph, endpoint, neighbor, &endpoints);
if let Some(&last) = path.last() {
if !endpoints.contains(&last) {
continue; }
let mut total_length = 0.0;
let mut total_walk = 0.0;
let mut total_bike = 0.0;
let mut total_drive = 0.0;
let mut speeds = Vec::new();
let mut all_tags: Vec<XmlTag> = Vec::new();
for window in path.windows(2) {
if let [u, v] = window {
if let Some(edge) = consolidated_graph.find_edge(*u, *v) {
let way = consolidated_graph.edge_weight(edge).unwrap();
total_length += way.length;
total_walk += way.walk_travel_time;
total_bike += way.bike_travel_time;
total_drive += way.drive_travel_time;
speeds.push(way.speed_kph);
all_tags.extend(way.tags.clone());
}
}
}
let avg_speed = if !speeds.is_empty() {
speeds.iter().sum::<f64>() / speeds.len() as f64
} else {
0.0
};
let collapsed_way = XmlWay {
id: get_unique_id(),
nodes: vec![],
tags: all_tags,
length: total_length,
speed_kph: avg_speed,
walk_travel_time: total_walk,
bike_travel_time: total_bike,
drive_travel_time: total_drive,
};
if let (Some(&new_src), Some(&new_dst)) =
(index_map.get(&endpoint), index_map.get(&last))
{
simplified_graph.add_edge(new_src, new_dst, collapsed_way);
added_edges.insert((endpoint, last));
}
}
}
}
deduplicate_edges(simplified_graph)
}
fn deduplicate_edges(graph: DiGraph<XmlNode, XmlWay>) -> DiGraph<XmlNode, XmlWay> {
let mut best: HashMap<(NodeIndex, NodeIndex), XmlWay> = HashMap::new();
for edge in graph.edge_references() {
let key = (edge.source(), edge.target());
let way = edge.weight();
best.entry(key)
.and_modify(|existing| {
if way.drive_travel_time < existing.drive_travel_time {
*existing = way.clone();
}
})
.or_insert_with(|| way.clone());
}
let mut deduped = DiGraph::new();
let mut node_map: HashMap<NodeIndex, NodeIndex> = HashMap::new();
for old_idx in graph.node_indices() {
let new_idx = deduped.add_node(graph[old_idx].clone());
node_map.insert(old_idx, new_idx);
}
for ((src, dst), way) in best {
deduped.add_edge(node_map[&src], node_map[&dst], way);
}
deduped
}
fn build_path(
graph: &DiGraph<XmlNode, XmlWay>,
start: NodeIndex,
first_step: NodeIndex,
endpoints: &HashSet<NodeIndex>,
) -> Vec<NodeIndex> {
let mut path = vec![start, first_step];
let mut current = first_step;
while !endpoints.contains(¤t) {
let next = graph
.neighbors_directed(current, petgraph::Outgoing)
.find(|&n| Some(&n) != path.iter().rev().nth(1));
match next {
Some(n) => {
path.push(n);
current = n;
}
None => break,
}
}
path
}
fn is_endpoint(graph: &DiGraph<XmlNode, XmlWay>, node_index: NodeIndex) -> bool {
let out_neighbors: HashSet<_> = graph
.neighbors_directed(node_index, petgraph::Outgoing)
.collect();
let in_neighbors: HashSet<_> = graph
.neighbors_directed(node_index, petgraph::Incoming)
.collect();
if out_neighbors.contains(&node_index) || in_neighbors.contains(&node_index) {
return true;
}
if out_neighbors.is_empty() || in_neighbors.is_empty() {
return true;
}
let total_neighbors: HashSet<_> = out_neighbors.union(&in_neighbors).collect();
let total_degree = total_neighbors.len();
total_degree != 2
}
fn consolidate_intersections(
graph: &DiGraph<XmlNode, XmlWay>,
precision: usize,
) -> (DiGraph<XmlNode, XmlWay>, HashMap<NodeIndex, NodeIndex>) {
let mut hash_to_old_indices: HashMap<String, Vec<NodeIndex>> = HashMap::new();
for node_idx in graph.node_indices() {
let node = &graph[node_idx];
let hash = encode((node.lat, node.lon).into(), precision).unwrap_or_default();
hash_to_old_indices.entry(hash).or_default().push(node_idx);
}
let mut new_graph = DiGraph::new();
let mut old_to_new: HashMap<NodeIndex, NodeIndex> = HashMap::new();
for (_hash, old_indices) in &hash_to_old_indices {
let merged = merge_nodes(graph, old_indices);
let new_idx = new_graph.add_node(merged);
for &old_idx in old_indices {
old_to_new.insert(old_idx, new_idx);
}
}
let mut seen_edges: HashSet<(NodeIndex, NodeIndex)> = HashSet::new();
for edge in graph.edge_references() {
let new_src = old_to_new[&edge.source()];
let new_dst = old_to_new[&edge.target()];
if new_src == new_dst {
continue; }
if seen_edges.insert((new_src, new_dst)) {
new_graph.add_edge(new_src, new_dst, edge.weight().clone());
}
}
(new_graph, old_to_new)
}
fn merge_nodes(graph: &DiGraph<XmlNode, XmlWay>, indices: &[NodeIndex]) -> XmlNode {
let nodes: Vec<&XmlNode> = indices.iter().map(|&i| &graph[i]).collect();
let count = nodes.len() as f64;
let avg_lat = nodes.iter().map(|n| n.lat).sum::<f64>() / count;
let avg_lon = nodes.iter().map(|n| n.lon).sum::<f64>() / count;
let merged_tags = nodes.iter().flat_map(|n| n.tags.clone()).collect();
let geohash = nodes[0].geohash.clone();
XmlNode {
id: get_unique_id(),
lat: avg_lat,
lon: avg_lon,
tags: merged_tags,
geohash,
}
}
fn get_unique_id() -> i64 {
ID_COUNTER.fetch_add(1, Ordering::Relaxed) as i64
}