use crate::graph::{EdgeId, NodeId, RoadGraph, RoadType};
pub struct Chain {
pub nodes: Vec<NodeId>,
pub road_type: RoadType,
pub edges: Vec<EdgeId>,
}
pub fn compute_active_degrees(graph: &RoadGraph) -> Vec<u32> {
let mut degrees = vec![0u32; graph.nodes.len()];
for edge in &graph.edges {
if !edge.active {
continue;
}
degrees[edge.start as usize] += 1;
degrees[edge.end as usize] += 1;
}
degrees
}
pub fn extract_chains(graph: &RoadGraph, degrees: &[u32]) -> Vec<Chain> {
let mut visited_edges = vec![false; graph.edges.len()];
let mut chains = Vec::new();
for (eid, edge) in graph.edges.iter().enumerate() {
if !edge.active || visited_edges[eid] {
continue;
}
let road_type = edge.road_type;
let mut chain_nodes = Vec::new();
let mut chain_edges = Vec::new();
visited_edges[eid] = true;
let (head_nodes, head_edges) = walk_chain(
graph,
degrees,
edge.start,
eid as u32,
&mut visited_edges,
road_type,
);
head_nodes
.into_iter()
.rev()
.for_each(|n| chain_nodes.push(n));
head_edges
.into_iter()
.rev()
.for_each(|e| chain_edges.push(e));
chain_nodes.push(edge.start);
chain_edges.push(eid as EdgeId);
chain_nodes.push(edge.end);
let (tail_nodes, tail_edges) = walk_chain(
graph,
degrees,
edge.end,
eid as u32,
&mut visited_edges,
road_type,
);
chain_nodes.extend(tail_nodes);
chain_edges.extend(tail_edges);
chain_nodes.dedup();
if chain_nodes.len() >= 2 {
chains.push(Chain {
nodes: chain_nodes,
road_type,
edges: chain_edges,
});
}
}
chains
}
pub fn extract_chains_any_type(graph: &RoadGraph, degrees: &[u32]) -> Vec<Chain> {
let mut visited_edges = vec![false; graph.edges.len()];
let mut chains = Vec::new();
for (eid, edge) in graph.edges.iter().enumerate() {
if !edge.active || visited_edges[eid] {
continue;
}
visited_edges[eid] = true;
let mut chain_nodes = Vec::new();
let mut chain_edges = Vec::new();
let (head_nodes, head_edges) =
walk_chain_any_type(graph, degrees, edge.start, eid as u32, &mut visited_edges);
head_nodes
.into_iter()
.rev()
.for_each(|n| chain_nodes.push(n));
head_edges
.into_iter()
.rev()
.for_each(|e| chain_edges.push(e));
chain_nodes.push(edge.start);
chain_edges.push(eid as EdgeId);
chain_nodes.push(edge.end);
let (tail_nodes, tail_edges) =
walk_chain_any_type(graph, degrees, edge.end, eid as u32, &mut visited_edges);
chain_nodes.extend(tail_nodes);
chain_edges.extend(tail_edges);
chain_nodes.dedup();
if chain_nodes.len() >= 2 {
chains.push(Chain {
nodes: chain_nodes,
road_type: edge.road_type,
edges: chain_edges,
});
}
}
chains
}
fn walk_chain_any_type(
graph: &RoadGraph,
degrees: &[u32],
start_node: NodeId,
from_edge: u32,
visited_edges: &mut [bool],
) -> (Vec<NodeId>, Vec<EdgeId>) {
let mut nodes = Vec::new();
let mut edges = Vec::new();
let mut current = start_node;
let mut prev_edge = from_edge;
if degrees[current as usize] != 2 {
return (nodes, edges);
}
loop {
let node = &graph.nodes[current as usize];
let mut next_edge = None;
for &eid in &node.edges {
if eid == prev_edge {
continue;
}
let e = &graph.edges[eid as usize];
if !e.active || visited_edges[eid as usize] {
continue;
}
next_edge = Some(eid);
break;
}
let Some(ne) = next_edge else { break };
visited_edges[ne as usize] = true;
edges.push(ne);
let next_node = graph.opposite(ne, current);
nodes.push(next_node);
if degrees[next_node as usize] != 2 {
break;
}
prev_edge = ne;
current = next_node;
}
(nodes, edges)
}
fn walk_chain(
graph: &RoadGraph,
degrees: &[u32],
start_node: NodeId,
from_edge: u32,
visited_edges: &mut [bool],
road_type: RoadType,
) -> (Vec<NodeId>, Vec<EdgeId>) {
let mut nodes = Vec::new();
let mut edges = Vec::new();
let mut current = start_node;
let mut prev_edge = from_edge;
if degrees[current as usize] != 2 {
return (nodes, edges);
}
loop {
let node = &graph.nodes[current as usize];
let mut next_edge = None;
for &eid in &node.edges {
if eid == prev_edge {
continue;
}
let e = &graph.edges[eid as usize];
if !e.active || visited_edges[eid as usize] {
continue;
}
if e.road_type != road_type {
continue;
}
next_edge = Some(eid);
break;
}
let Some(ne) = next_edge else { break };
visited_edges[ne as usize] = true;
edges.push(ne);
let next_node = graph.opposite(ne, current);
nodes.push(next_node);
if degrees[next_node as usize] != 2 {
break;
}
prev_edge = ne;
current = next_node;
}
(nodes, edges)
}
pub struct Artery {
pub nodes: Vec<NodeId>,
pub road_type: RoadType,
pub edges: Vec<EdgeId>,
}
const ARTERY_MIN_ALIGNMENT: f32 = 0.5;
pub fn extract_arteries(graph: &RoadGraph, degrees: &[u32], road_type: RoadType) -> Vec<Artery> {
let mut visited_edges = vec![false; graph.edges.len()];
let mut arteries = Vec::new();
for (eid, edge) in graph.edges.iter().enumerate() {
if !edge.active || visited_edges[eid] || edge.road_type != road_type {
continue;
}
visited_edges[eid] = true;
let mut visited_nodes = vec![false; graph.nodes.len()];
visited_nodes[edge.start as usize] = true;
visited_nodes[edge.end as usize] = true;
let (mut head_nodes, mut head_edges) = walk_artery(
graph,
degrees,
edge.start,
edge.end, eid as EdgeId,
&mut visited_edges,
&mut visited_nodes,
road_type,
);
head_nodes.reverse();
head_edges.reverse();
head_nodes.push(edge.start);
head_edges.push(eid as EdgeId);
head_nodes.push(edge.end);
let (tail_nodes, tail_edges) = walk_artery(
graph,
degrees,
edge.end,
edge.start, eid as EdgeId,
&mut visited_edges,
&mut visited_nodes,
road_type,
);
head_nodes.extend(tail_nodes);
head_edges.extend(tail_edges);
head_nodes.dedup();
if head_nodes.len() >= 2 {
arteries.push(Artery {
nodes: head_nodes,
road_type,
edges: head_edges,
});
}
}
arteries
}
#[allow(clippy::too_many_arguments)]
fn walk_artery(
graph: &RoadGraph,
degrees: &[u32],
current_node: NodeId,
came_from_node: NodeId,
from_edge: EdgeId,
visited_edges: &mut [bool],
visited_nodes: &mut [bool],
road_type: RoadType,
) -> (Vec<NodeId>, Vec<EdgeId>) {
let mut nodes = Vec::new();
let mut edges = Vec::new();
let mut current = current_node;
let mut prev_node = came_from_node;
let mut prev_edge = from_edge;
loop {
let deg = degrees[current as usize];
if deg <= 1 {
break;
}
if deg == 2 {
let node = &graph.nodes[current as usize];
let mut next_edge = None;
for &eid in &node.edges {
if eid == prev_edge {
continue;
}
let e = &graph.edges[eid as usize];
if !e.active || visited_edges[eid as usize] || e.road_type != road_type {
continue;
}
next_edge = Some(eid);
break;
}
let Some(ne) = next_edge else { break };
let next_node = graph.opposite(ne, current);
if visited_nodes[next_node as usize] {
break;
}
visited_edges[ne as usize] = true;
visited_nodes[next_node as usize] = true;
edges.push(ne);
nodes.push(next_node);
prev_node = current;
prev_edge = ne;
current = next_node;
continue;
}
let forward = (graph.node_pos(current) - graph.node_pos(prev_node)).normalize_or_zero();
if forward.length_squared() < 1e-12 {
break;
}
let node = &graph.nodes[current as usize];
let mut best_edge: Option<EdgeId> = None;
let mut best_cos = ARTERY_MIN_ALIGNMENT;
for &eid in &node.edges {
if eid == prev_edge {
continue;
}
let e = &graph.edges[eid as usize];
if !e.active || visited_edges[eid as usize] || e.road_type != road_type {
continue;
}
let neighbor = graph.opposite(eid, current);
if visited_nodes[neighbor as usize] {
continue;
}
let dir = (graph.node_pos(neighbor) - graph.node_pos(current)).normalize_or_zero();
let cos = forward.dot(dir);
if cos > best_cos {
best_cos = cos;
best_edge = Some(eid);
}
}
let Some(ne) = best_edge else { break };
let next_node = graph.opposite(ne, current);
visited_edges[ne as usize] = true;
visited_nodes[next_node as usize] = true;
edges.push(ne);
nodes.push(next_node);
prev_node = current;
prev_edge = ne;
current = next_node;
}
(nodes, edges)
}