#[cfg(test)]
#[allow(clippy::module_inception)]
mod tests {
use crate::graph::planar_graph::PlanarGraph;
use crate::types::Line3D;
use geo_types::{Coord, LineString};
#[test]
fn test_graph_construction() {
let mut graph = PlanarGraph::new();
let l1 = LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]);
let l2 = LineString::from(vec![(0.0, 0.0), (0.0, 10.0)]);
graph.add_line_string(l1);
graph.add_line_string(l2);
assert_eq!(graph.nodes_x.len(), 3); assert_eq!(graph.edges.len(), 2);
assert_eq!(graph.directed_edges.len(), 4);
let center_node_idx = graph.node_map.get(&Coord::from((0.0, 0.0)).into()).unwrap();
assert_eq!(graph.nodes_outgoing[*center_node_idx].len(), 2);
}
#[test]
fn test_bulk_load_duplicate_nodes_different_z() {
use crate::types::Coord3D;
let mut graph = PlanarGraph::new();
let p1_a = Coord3D {
x: 5.0,
y: 5.0,
z: 10.0,
};
let p1_b = Coord3D {
x: 5.0,
y: 5.0,
z: 20.0,
};
let p2_a = Coord3D {
x: 10.0,
y: 10.0,
z: 10.0,
};
let p2_b = Coord3D {
x: 10.0,
y: 10.0,
z: 30.0,
};
let lines = vec![Line3D::new(p1_a, p2_a, 0), Line3D::new(p1_b, p2_b, 1)];
graph.bulk_load(lines);
assert_eq!(graph.nodes_x.len(), 2);
assert_eq!(graph.edges.len(), 1);
assert_eq!(graph.directed_edges.len(), 2);
assert_eq!(graph.edges[0].sources.line_ids.as_slice(), &[0, 1]);
let mut n1 = [graph.directed_edges[0].src, graph.directed_edges[0].dst];
n1.sort_unstable();
assert_ne!(n1[0], n1[1]);
}
#[test]
fn test_incremental_edges_merge_and_remove_sources() {
use crate::types::Coord3D;
let mut graph = PlanarGraph::new();
let start = Coord3D::new(0.0, 0.0, 0.0);
let end = Coord3D::new(1.0, 0.0, 0.0);
let first = graph.add_line(Line3D::new(start, end, 10));
let second = graph.add_line(Line3D::new(end, start, 20));
assert_eq!(first, second);
assert_eq!(graph.edges.len(), 1);
assert_eq!(graph.edges[0].sources.line_ids.as_slice(), &[10, 20]);
assert!(graph.remove_line_by_id(10));
assert!(!graph.edges[0].deleted);
assert_eq!(graph.edges[0].line.line_id, 20);
assert!(graph.remove_line_by_id(20));
assert!(graph.edges[0].deleted);
}
#[test]
fn test_edge_sorting() {
let mut graph = PlanarGraph::new();
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (0.0, 10.0)]));
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (-10.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (0.0, -10.0)]));
graph.sort_edges();
let center_node_idx = graph.node_map.get(&Coord::from((0.0, 0.0)).into()).unwrap();
let edges = &graph.nodes_outgoing[*center_node_idx];
assert_eq!(edges.len(), 4);
let get_dst = |idx: usize| -> (f64, f64) {
let dst_node_idx = graph.directed_edges[idx].dst;
(graph.nodes_x[dst_node_idx], graph.nodes_y[dst_node_idx])
};
let dst0 = get_dst(edges[0]);
let dst1 = get_dst(edges[1]);
let dst2 = get_dst(edges[2]);
let dst3 = get_dst(edges[3]);
assert!(
dst0.0 > 0.0 && dst0.1.abs() < 1e-6,
"Expected Right (10, 0), got {:?}",
dst0
);
assert!(
dst1.0.abs() < 1e-6 && dst1.1 > 0.0,
"Expected Up (0, 10), got {:?}",
dst1
);
assert!(
dst2.0 < 0.0 && dst2.1.abs() < 1e-6,
"Expected Left (-10, 0), got {:?}",
dst2
);
assert!(
dst3.0.abs() < 1e-6 && dst3.1 < 0.0,
"Expected Down (0, -10), got {:?}",
dst3
);
}
#[test]
fn test_dangle_pruning() {
let mut graph = PlanarGraph::new();
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(10.0, 0.0), (0.0, 10.0)]));
graph.add_line_string(LineString::from(vec![(0.0, 10.0), (0.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(10.0, 0.0), (20.0, 0.0)]));
graph.sort_edges();
let dangles = graph.prune_dangles();
assert_eq!(dangles.len(), 1);
let b_idx = graph
.node_map
.get(&Coord::from((10.0, 0.0)).into())
.unwrap();
assert_eq!(graph.nodes_degree[*b_idx], 2);
}
#[test]
fn test_simple_cycle() {
let mut graph = PlanarGraph::new();
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(10.0, 0.0), (0.0, 10.0)]));
graph.add_line_string(LineString::from(vec![(0.0, 10.0), (0.0, 0.0)]));
graph.sort_edges();
let rings = graph.get_edge_rings();
assert_eq!(rings.len(), 2);
}
#[test]
fn test_bulk_load() {
use geo::Line;
let segments = vec![
Line::new(Coord::from((0.0, 0.0)), Coord::from((10.0, 0.0))),
Line::new(Coord::from((10.0, 0.0)), Coord::from((10.0, 10.0))),
Line::new(Coord::from((10.0, 10.0)), Coord::from((0.0, 10.0))),
Line::new(Coord::from((0.0, 10.0)), Coord::from((0.0, 0.0))),
Line::new(Coord::from((0.0, 0.0)), Coord::from((10.0, 10.0))), Line::new(Coord::from((20.0, 20.0)), Coord::from((30.0, 30.0))), ];
let mut graph_incremental = PlanarGraph::new();
for segment in &segments {
graph_incremental.add_line_string(LineString::from(vec![segment.start, segment.end]));
}
let mut graph_bulk = PlanarGraph::new();
let segments_3d: Vec<Line3D> = segments.iter().map(|l| (*l).into()).collect();
graph_bulk.bulk_load(segments_3d);
assert_eq!(
graph_bulk.nodes_x.len(),
graph_incremental.nodes_x.len(),
"Node count mismatch"
);
assert_eq!(
graph_bulk.edges.len(),
graph_incremental.edges.len(),
"Edge count mismatch"
);
assert_eq!(
graph_bulk.directed_edges.len(),
graph_incremental.directed_edges.len(),
"Directed edge count mismatch"
);
let get_neighbors = |graph: &PlanarGraph, coord: Coord<f64>| -> Vec<Coord<f64>> {
let mut node_idx = graph.node_map.get(&coord.into()).copied();
if node_idx.is_none() {
for (i, (&x, &y)) in graph.nodes_x.iter().zip(graph.nodes_y.iter()).enumerate() {
if x == coord.x && y == coord.y {
node_idx = Some(i);
break;
}
}
}
if let Some(idx) = node_idx {
let mut neighbors: Vec<Coord<f64>> = graph.nodes_outgoing[idx]
.iter()
.map(|&de_idx| {
let dst_idx = graph.directed_edges[de_idx].dst;
Coord {
x: graph.nodes_x[dst_idx],
y: graph.nodes_y[dst_idx],
}
})
.collect();
neighbors.sort_by(|a, b| {
a.x.partial_cmp(&b.x)
.unwrap()
.then(a.y.partial_cmp(&b.y).unwrap())
});
neighbors
} else {
vec![]
}
};
let mut unique_points: Vec<Coord<f64>> = segments
.iter()
.flat_map(|line| vec![line.start, line.end])
.collect();
unique_points.sort_by(|a, b| {
a.x.partial_cmp(&b.x)
.unwrap()
.then(a.y.partial_cmp(&b.y).unwrap())
});
unique_points.dedup();
for point in unique_points {
let neighbors_inc = get_neighbors(&graph_incremental, point);
let neighbors_bulk = get_neighbors(&graph_bulk, point);
assert_eq!(
neighbors_inc, neighbors_bulk,
"Neighbors mismatch for point {:?}",
point
);
}
}
#[test]
fn test_bulk_load_empty() {
let mut graph = PlanarGraph::new();
let lines: Vec<Line3D> = vec![];
graph.bulk_load(lines);
assert!(graph.nodes_x.is_empty());
assert!(graph.edges.is_empty());
assert!(graph.directed_edges.is_empty());
assert!(graph.nodes_outgoing.is_empty());
assert!(graph.node_map.is_empty());
}
#[test]
fn test_bulk_load_zero_length_segment() {
use crate::types::Coord3D;
let mut graph = PlanarGraph::new();
let p0 = Coord3D {
x: 0.0,
y: 0.0,
z: 0.0,
};
let p1 = Coord3D {
x: 0.0,
y: 0.0,
z: 0.0,
};
let p2 = Coord3D {
x: 1e-13,
y: 1e-13,
z: 0.0,
};
let p3 = Coord3D {
x: 10.0,
y: 10.0,
z: 0.0,
};
let lines = vec![
Line3D::new(p0, p1, 0),
Line3D::new(p0, p2, 1),
Line3D::new(p0, p3, 2),
];
graph.bulk_load(lines);
assert_eq!(graph.nodes_x.len(), 3);
assert_eq!(graph.edges.len(), 2);
assert_eq!(graph.directed_edges.len(), 4);
}
#[test]
fn test_get_cut_edges() {
let mut graph = PlanarGraph::new();
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(10.0, 0.0), (0.0, 10.0)]));
graph.add_line_string(LineString::from(vec![(0.0, 10.0), (0.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(10.0, 0.0), (20.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(20.0, 0.0), (30.0, 0.0)]));
graph.add_line_string(LineString::from(vec![(30.0, 0.0), (20.0, 10.0)]));
graph.add_line_string(LineString::from(vec![(20.0, 10.0), (20.0, 0.0)]));
graph.sort_edges();
let dangles = graph.prune_dangles();
assert_eq!(dangles.len(), 0);
let cut_edges = graph.delete_cut_edges();
assert_eq!(cut_edges.len(), 1);
let rings = graph.get_edge_rings();
assert_eq!(rings.len(), 4);
}
#[test]
fn test_get_cut_edges_simple() {
let mut graph = PlanarGraph::new();
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]));
let cut_edges = graph.delete_cut_edges();
assert_eq!(cut_edges.len(), 1);
let edge = &cut_edges[0];
assert_eq!(edge.len(), 2);
let c1 = edge[0];
let c2 = edge[1];
assert!(
(c1.x == 0.0 && c1.y == 0.0 && c2.x == 10.0 && c2.y == 0.0)
|| (c1.x == 10.0 && c1.y == 0.0 && c2.x == 0.0 && c2.y == 0.0)
);
let mut graph = PlanarGraph::new();
graph.add_line_string(LineString::from(vec![(0.0, 0.0), (10.0, 0.0)]));
graph.prune_dangles();
let cut_edges_after_prune = graph.delete_cut_edges();
assert_eq!(cut_edges_after_prune.len(), 0);
}
}