use crate::geometry::closest_point_on_segment;
use crate::graph::{EdgeId, NodeId, RoadGraph, RoadType};
use crate::lots::BuildingLot;
use crate::rationalize::unify_road_types;
use std::cmp::Ordering;
use std::collections::{BTreeSet, BinaryHeap, HashMap, HashSet};
#[derive(Copy, Clone, PartialEq)]
struct State {
cost: f32,
node: NodeId,
}
impl Eq for State {}
impl Ord for State {
fn cmp(&self, other: &Self) -> Ordering {
other
.cost
.partial_cmp(&self.cost)
.unwrap_or(Ordering::Equal)
}
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub fn prune_unused_roads(graph: &mut RoadGraph, lots: &[BuildingLot]) {
if lots.is_empty() {
for edge in &mut graph.edges {
edge.active = false;
}
return;
}
let mut comp_ids = vec![0; graph.nodes.len()];
let mut comp_sizes = HashMap::new();
let mut current_comp = 1;
for i in 0..graph.nodes.len() {
if comp_ids[i] == 0 {
let mut stack = vec![i as NodeId];
let mut size = 0;
while let Some(node) = stack.pop() {
if comp_ids[node as usize] == 0 {
comp_ids[node as usize] = current_comp;
size += 1;
for &eid in &graph.nodes[node as usize].edges {
let e = &graph.edges[eid as usize];
if e.active {
let opp = graph.opposite(eid, node);
if comp_ids[opp as usize] == 0 {
stack.push(opp);
}
}
}
}
}
comp_sizes.insert(current_comp, size);
current_comp += 1;
}
}
let max_comp_size = comp_sizes.values().copied().max().unwrap_or(0);
let mut essential_edges = HashSet::new();
let mut essential_nodes = BTreeSet::new();
for lot in lots {
let mut best_edge = None;
let mut best_score = f32::MAX;
for (i, edge) in graph.edges.iter().enumerate() {
if !edge.active {
continue;
}
let a = graph.nodes[edge.start as usize].position;
let b = graph.nodes[edge.end as usize].position;
let proj = closest_point_on_segment(lot.frontage_center, a, b);
let dist = lot.frontage_center.distance(proj);
let comp_id = comp_ids[edge.start as usize];
let comp_size = comp_sizes[&comp_id];
let penalty = if comp_size < 3 && max_comp_size > 20 {
1000.0
} else {
0.0
};
let score = dist + penalty;
if score < best_score {
best_score = score;
best_edge = Some(i as EdgeId);
}
}
if let Some(eid) = best_edge {
essential_edges.insert(eid);
let edge = &graph.edges[eid as usize];
essential_nodes.insert(edge.start);
essential_nodes.insert(edge.end);
}
}
let mut keep_edges = essential_edges.clone();
let mut connected_nodes = BTreeSet::new();
let mut unreached_essential = essential_nodes.clone();
if let Some(&start_node) = essential_nodes.iter().next() {
connected_nodes.insert(start_node);
unreached_essential.remove(&start_node);
}
let num_nodes = graph.nodes.len();
let mut heap = BinaryHeap::new();
let mut dist_gen = vec![(0_u32, f32::MAX); num_nodes];
let mut from_gen: Vec<(u32, Option<(NodeId, EdgeId)>)> = vec![(0, None); num_nodes];
let mut current_gen = 0_u32;
while !unreached_essential.is_empty() {
heap.clear();
current_gen += 1;
for &node in &connected_nodes {
dist_gen[node as usize] = (current_gen, 0.0);
heap.push(State { cost: 0.0, node });
}
let mut found_target = None;
while let Some(State { cost, node }) = heap.pop() {
if unreached_essential.contains(&node) {
found_target = Some(node);
break;
}
let cur_dist = dist_gen[node as usize];
if cur_dist.0 != current_gen || cost > cur_dist.1 {
continue;
}
for &edge_id in &graph.nodes[node as usize].edges {
let edge = &graph.edges[edge_id as usize];
if !edge.active {
continue;
}
let next_node = graph.opposite(edge_id, node);
let dist = graph.nodes[node as usize]
.position
.distance(graph.nodes[next_node as usize].position);
let weight = if edge.road_type == RoadType::Major {
dist * 1.0
} else {
dist * 5.0
};
let next_cost = cost + weight;
let nd = &mut dist_gen[next_node as usize];
if nd.0 != current_gen || next_cost < nd.1 {
*nd = (current_gen, next_cost);
from_gen[next_node as usize] = (current_gen, Some((node, edge_id)));
heap.push(State {
cost: next_cost,
node: next_node,
});
}
}
}
if let Some(mut curr) = found_target {
while from_gen[curr as usize].0 == current_gen {
let Some((prev, edge_id)) = from_gen[curr as usize].1 else {
break;
};
keep_edges.insert(edge_id);
connected_nodes.insert(curr);
unreached_essential.remove(&curr);
curr = prev;
}
connected_nodes.insert(curr);
unreached_essential.remove(&curr);
} else {
let target_comp = comp_ids[*unreached_essential.iter().next().unwrap() as usize];
let island_seed: Vec<NodeId> = unreached_essential
.iter()
.copied()
.filter(|&n| comp_ids[n as usize] == target_comp)
.collect();
if island_seed.is_empty() {
break;
}
let island_comp = comp_ids[island_seed[0] as usize];
let mut island_connected: BTreeSet<NodeId> = BTreeSet::new();
island_connected.insert(island_seed[0]);
let mut island_remaining: BTreeSet<NodeId> = island_seed.iter().copied().collect();
island_remaining.remove(&island_seed[0]);
while !island_remaining.is_empty() {
heap.clear();
current_gen += 1;
for &node in &island_connected {
dist_gen[node as usize] = (current_gen, 0.0);
heap.push(State { cost: 0.0, node });
}
let mut found = None;
while let Some(State { cost, node }) = heap.pop() {
if island_remaining.contains(&node) {
found = Some(node);
break;
}
let cur_dist = dist_gen[node as usize];
if cur_dist.0 != current_gen || cost > cur_dist.1 {
continue;
}
for &edge_id in &graph.nodes[node as usize].edges {
let edge = &graph.edges[edge_id as usize];
if !edge.active {
continue;
}
let next = graph.opposite(edge_id, node);
if comp_ids[next as usize] != island_comp {
continue;
}
let d = graph.nodes[node as usize]
.position
.distance(graph.nodes[next as usize].position);
let next_cost = cost + d;
let nd = &mut dist_gen[next as usize];
if nd.0 != current_gen || next_cost < nd.1 {
*nd = (current_gen, next_cost);
from_gen[next as usize] = (current_gen, Some((node, edge_id)));
heap.push(State {
cost: next_cost,
node: next,
});
}
}
}
if let Some(mut curr) = found {
while from_gen[curr as usize].0 == current_gen {
let Some((prev, edge_id)) = from_gen[curr as usize].1 else {
break;
};
keep_edges.insert(edge_id);
island_connected.insert(curr);
island_remaining.remove(&curr);
curr = prev;
}
island_connected.insert(curr);
island_remaining.remove(&curr);
} else {
break;
}
}
for n in &island_seed {
unreached_essential.remove(n);
}
}
}
for (i, edge) in graph.edges.iter_mut().enumerate() {
if !keep_edges.contains(&(i as EdgeId)) {
edge.active = false;
}
}
unify_road_types(graph);
}