1use super::graph::{Edge, EdgeId, Graph, GraphType, Node, NodeId};
7use crate::error::{AlgorithmError, Result};
8use oxigdal_core::vector::Coordinate;
9use std::collections::{HashMap, HashSet, VecDeque};
10
11#[derive(Debug, Clone)]
13pub struct ValidationResult {
14 pub is_valid: bool,
16 pub issues: Vec<ValidationIssue>,
18}
19
20#[derive(Debug, Clone)]
22pub struct ValidationIssue {
23 pub severity: ValidationSeverity,
25 pub description: String,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ValidationSeverity {
32 Error,
34 Warning,
36 Info,
38}
39
40#[derive(Debug, Clone)]
42pub struct ConnectedComponent {
43 pub nodes: Vec<NodeId>,
45 pub edge_count: usize,
47}
48
49#[derive(Debug, Clone)]
51pub struct TopologyCleanResult {
52 pub nodes_snapped: usize,
54 pub self_loops_removed: usize,
56 pub parallel_edges_removed: usize,
58 pub isolated_nodes_removed: usize,
60 pub degree2_nodes_contracted: usize,
62}
63
64#[derive(Debug, Clone)]
66pub struct NetworkNode {
67 pub node: Node,
69 pub node_type: String,
71 pub elevation: Option<f64>,
73}
74
75#[derive(Debug, Clone)]
77pub struct NetworkEdge {
78 pub edge: Edge,
80 pub road_type: String,
82 pub one_way: bool,
84 pub surface: Option<String>,
86}
87
88#[derive(Debug, Clone)]
93pub struct TimeDependentWeight {
94 pub intervals: Vec<(f64, f64)>,
97}
98
99impl TimeDependentWeight {
100 pub fn constant() -> Self {
102 Self {
103 intervals: vec![(0.0, 1.0)],
104 }
105 }
106
107 pub fn new(mut intervals: Vec<(f64, f64)>) -> Self {
109 intervals.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
110 if intervals.is_empty() {
111 intervals.push((0.0, 1.0));
112 }
113 Self { intervals }
114 }
115
116 pub fn multiplier_at(&self, time: f64) -> f64 {
118 let time = time % 86400.0;
120
121 let mut result = self.intervals[0].1;
123 for &(start, mult) in &self.intervals {
124 if time >= start {
125 result = mult;
126 } else {
127 break;
128 }
129 }
130 result
131 }
132
133 pub fn rush_hour() -> Self {
135 Self::new(vec![
136 (0.0, 0.8), (25200.0, 1.5), (32400.0, 1.0), (61200.0, 1.5), (68400.0, 1.0), (79200.0, 0.8), ])
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct TurnPenalty {
149 pub via_node: NodeId,
151 pub from_edge: EdgeId,
153 pub to_edge: EdgeId,
155 pub penalty: f64,
157}
158
159#[derive(Debug, Clone, Default)]
161pub struct TurnPenalties {
162 penalties: HashMap<(NodeId, EdgeId, EdgeId), f64>,
164 prohibited: HashSet<(NodeId, EdgeId, EdgeId)>,
166}
167
168impl TurnPenalties {
169 pub fn new() -> Self {
171 Self::default()
172 }
173
174 pub fn add_penalty(
176 &mut self,
177 via_node: NodeId,
178 from_edge: EdgeId,
179 to_edge: EdgeId,
180 penalty: f64,
181 ) {
182 self.penalties
183 .insert((via_node, from_edge, to_edge), penalty);
184 }
185
186 pub fn add_prohibition(&mut self, via_node: NodeId, from_edge: EdgeId, to_edge: EdgeId) {
188 self.prohibited.insert((via_node, from_edge, to_edge));
189 }
190
191 pub fn get_penalty(&self, via_node: NodeId, from_edge: EdgeId, to_edge: EdgeId) -> f64 {
193 if self.prohibited.contains(&(via_node, from_edge, to_edge)) {
194 return f64::INFINITY;
195 }
196 self.penalties
197 .get(&(via_node, from_edge, to_edge))
198 .copied()
199 .unwrap_or(0.0)
200 }
201
202 pub fn is_prohibited(&self, via_node: NodeId, from_edge: EdgeId, to_edge: EdgeId) -> bool {
204 self.prohibited.contains(&(via_node, from_edge, to_edge))
205 }
206
207 pub fn len(&self) -> usize {
209 self.penalties.len() + self.prohibited.len()
210 }
211
212 pub fn is_empty(&self) -> bool {
214 self.penalties.is_empty() && self.prohibited.is_empty()
215 }
216}
217
218pub fn haversine_distance(a: &Coordinate, b: &Coordinate) -> f64 {
220 const EARTH_RADIUS: f64 = 6_371_000.0; let lat1 = a.y.to_radians();
223 let lat2 = b.y.to_radians();
224 let dlat = (b.y - a.y).to_radians();
225 let dlon = (b.x - a.x).to_radians();
226
227 let h = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2);
228
229 2.0 * EARTH_RADIUS * h.sqrt().asin()
230}
231
232impl Graph {
235 pub fn validate(&self) -> Result<()> {
237 let result = self.validate_detailed();
238 if result.is_valid {
239 Ok(())
240 } else {
241 let errors: Vec<String> = result
242 .issues
243 .iter()
244 .filter(|i| i.severity == ValidationSeverity::Error)
245 .map(|i| i.description.clone())
246 .collect();
247 Err(AlgorithmError::InvalidGeometry(errors.join("; ")))
248 }
249 }
250
251 pub fn validate_detailed(&self) -> ValidationResult {
253 let mut issues = Vec::new();
254
255 for (edge_id, edge) in self.edges_iter() {
257 if !self.has_node(edge.source) {
258 issues.push(ValidationIssue {
259 severity: ValidationSeverity::Error,
260 description: format!(
261 "Edge {:?} references non-existent source node {:?}",
262 edge_id, edge.source
263 ),
264 });
265 }
266
267 if !self.has_node(edge.target) {
268 issues.push(ValidationIssue {
269 severity: ValidationSeverity::Error,
270 description: format!(
271 "Edge {:?} references non-existent target node {:?}",
272 edge_id, edge.target
273 ),
274 });
275 }
276
277 if edge.is_self_loop() {
278 issues.push(ValidationIssue {
279 severity: ValidationSeverity::Warning,
280 description: format!(
281 "Edge {:?} is a self-loop at node {:?}",
282 edge_id, edge.source
283 ),
284 });
285 }
286
287 if edge.weight < 0.0 {
288 issues.push(ValidationIssue {
289 severity: ValidationSeverity::Warning,
290 description: format!("Edge {:?} has negative weight {}", edge_id, edge.weight),
291 });
292 }
293
294 if edge.weight.is_nan() || edge.weight.is_infinite() {
295 issues.push(ValidationIssue {
296 severity: ValidationSeverity::Error,
297 description: format!("Edge {:?} has invalid weight (NaN or infinite)", edge_id),
298 });
299 }
300 }
301
302 let isolated_count = self
304 .node_ids()
305 .iter()
306 .filter(|node_id| {
307 self.outgoing_edges(**node_id).is_empty()
308 && self.incoming_edges(**node_id).is_empty()
309 })
310 .count();
311
312 if isolated_count > 0 {
313 issues.push(ValidationIssue {
314 severity: ValidationSeverity::Info,
315 description: format!("Graph contains {} isolated node(s)", isolated_count),
316 });
317 }
318
319 let mut edge_pairs: HashMap<(NodeId, NodeId), Vec<EdgeId>> = HashMap::new();
321 for (&edge_id, edge) in self.edges_iter() {
322 let key = if self.graph_type() == GraphType::Undirected {
323 if edge.source.0 <= edge.target.0 {
324 (edge.source, edge.target)
325 } else {
326 (edge.target, edge.source)
327 }
328 } else {
329 (edge.source, edge.target)
330 };
331 edge_pairs.entry(key).or_default().push(edge_id);
332 }
333 for ((src, tgt), ids) in &edge_pairs {
334 if ids.len() > 1 {
335 issues.push(ValidationIssue {
336 severity: ValidationSeverity::Warning,
337 description: format!(
338 "Parallel edges detected between {:?} and {:?}: {:?}",
339 src, tgt, ids
340 ),
341 });
342 }
343 }
344
345 let is_valid = !issues
346 .iter()
347 .any(|i| i.severity == ValidationSeverity::Error);
348
349 ValidationResult { is_valid, issues }
350 }
351
352 pub fn is_connected(&self) -> bool {
354 if self.num_nodes() == 0 {
355 return true;
356 }
357
358 let components = self.connected_components();
359 components.len() <= 1
360 }
361
362 pub fn connected_components(&self) -> Vec<ConnectedComponent> {
364 let mut visited: HashSet<NodeId> = HashSet::new();
365 let mut components = Vec::new();
366
367 for &node_id in &self.node_ids() {
368 if visited.contains(&node_id) {
369 continue;
370 }
371
372 let mut component_nodes = Vec::new();
373 let mut queue = VecDeque::new();
374 queue.push_back(node_id);
375 visited.insert(node_id);
376
377 while let Some(current) = queue.pop_front() {
378 component_nodes.push(current);
379
380 for &edge_id in self.outgoing_edges(current) {
381 if let Some(edge) = self.get_edge(edge_id) {
382 let neighbor = if self.graph_type() == GraphType::Undirected {
383 edge.other_node(current)
384 } else {
385 Some(edge.target)
386 };
387 if let Some(n) = neighbor {
388 if !visited.contains(&n) {
389 visited.insert(n);
390 queue.push_back(n);
391 }
392 }
393 }
394 }
395
396 if self.graph_type() == GraphType::Directed {
397 for &edge_id in self.incoming_edges(current) {
398 if let Some(edge) = self.get_edge(edge_id) {
399 if !visited.contains(&edge.source) {
400 visited.insert(edge.source);
401 queue.push_back(edge.source);
402 }
403 }
404 }
405 }
406 }
407
408 let component_node_set: HashSet<NodeId> = component_nodes.iter().copied().collect();
409 let edge_count = self
410 .edges_iter()
411 .filter(|(_, e)| {
412 component_node_set.contains(&e.source) && component_node_set.contains(&e.target)
413 })
414 .count();
415
416 components.push(ConnectedComponent {
417 nodes: component_nodes,
418 edge_count,
419 });
420 }
421
422 components
423 }
424
425 pub fn strongly_connected_components(&self) -> Vec<Vec<NodeId>> {
427 if self.graph_type() == GraphType::Undirected {
428 return self
429 .connected_components()
430 .into_iter()
431 .map(|c| c.nodes)
432 .collect();
433 }
434
435 let mut index_counter: usize = 0;
436 let mut stack: Vec<NodeId> = Vec::new();
437 let mut on_stack: HashSet<NodeId> = HashSet::new();
438 let mut index_map: HashMap<NodeId, usize> = HashMap::new();
439 let mut lowlink: HashMap<NodeId, usize> = HashMap::new();
440 let mut sccs: Vec<Vec<NodeId>> = Vec::new();
441
442 let node_ids: Vec<NodeId> = self.node_ids();
443
444 for node in &node_ids {
445 if !index_map.contains_key(node) {
446 tarjan_dfs(
447 self,
448 *node,
449 &mut index_counter,
450 &mut stack,
451 &mut on_stack,
452 &mut index_map,
453 &mut lowlink,
454 &mut sccs,
455 );
456 }
457 }
458
459 sccs
460 }
461
462 pub fn remove_isolated_nodes(&mut self) -> Vec<NodeId> {
466 let isolated: Vec<NodeId> = self
467 .node_ids()
468 .into_iter()
469 .filter(|node_id| {
470 self.outgoing_edges(*node_id).is_empty() && self.incoming_edges(*node_id).is_empty()
471 })
472 .collect();
473
474 for node_id in &isolated {
475 self.remove_node_unchecked(*node_id);
476 }
477
478 isolated
479 }
480
481 pub fn remove_self_loops(&mut self) -> Vec<EdgeId> {
483 let self_loops: Vec<EdgeId> = self
484 .edges_iter()
485 .filter(|(_, e)| e.is_self_loop())
486 .map(|(&id, _)| id)
487 .collect();
488
489 let mut removed = Vec::new();
490 for edge_id in self_loops {
491 if self.remove_edge(edge_id).is_ok() {
492 removed.push(edge_id);
493 }
494 }
495
496 removed
497 }
498
499 pub fn remove_parallel_edges(&mut self) -> Vec<EdgeId> {
501 let mut edge_groups: HashMap<(NodeId, NodeId), Vec<(EdgeId, f64)>> = HashMap::new();
502
503 for (&edge_id, edge) in self.edges_iter() {
504 let key = if self.graph_type() == GraphType::Undirected {
505 if edge.source.0 <= edge.target.0 {
506 (edge.source, edge.target)
507 } else {
508 (edge.target, edge.source)
509 }
510 } else {
511 (edge.source, edge.target)
512 };
513 edge_groups
514 .entry(key)
515 .or_default()
516 .push((edge_id, edge.weight));
517 }
518
519 let mut removed = Vec::new();
520 for (_key, mut group) in edge_groups {
521 if group.len() <= 1 {
522 continue;
523 }
524 group.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
525 for &(edge_id, _) in &group[1..] {
526 if self.remove_edge(edge_id).is_ok() {
527 removed.push(edge_id);
528 }
529 }
530 }
531
532 removed
533 }
534
535 pub fn contract_degree2_nodes(&mut self) -> usize {
537 let mut contracted = 0;
538
539 loop {
540 let candidate = self.node_ids().into_iter().find(|&node_id| {
541 let out_edges = self.outgoing_edges(node_id);
542 let in_edges = self.incoming_edges(node_id);
543
544 if self.graph_type() == GraphType::Undirected {
545 out_edges.len() == 2
546 } else {
547 out_edges.len() == 1
548 && in_edges.len() == 1
549 && self
550 .get_edge(out_edges[0])
551 .map(|e| e.target)
552 .unwrap_or(node_id)
553 != self
554 .get_edge(in_edges[0])
555 .map(|e| e.source)
556 .unwrap_or(node_id)
557 }
558 });
559
560 let node_id = match candidate {
561 Some(n) => n,
562 None => break,
563 };
564
565 if self.graph_type() == GraphType::Directed {
566 let in_edges = self.incoming_edges(node_id).to_vec();
567 let out_edges = self.outgoing_edges(node_id).to_vec();
568
569 if in_edges.len() != 1 || out_edges.len() != 1 {
570 break;
571 }
572
573 let in_edge = match self.get_edge(in_edges[0]) {
574 Some(e) => e.clone(),
575 None => break,
576 };
577 let out_edge = match self.get_edge(out_edges[0]) {
578 Some(e) => e.clone(),
579 None => break,
580 };
581
582 let new_weight = in_edge.weight + out_edge.weight;
583 let source = in_edge.source;
584 let target = out_edge.target;
585
586 let _ = self.remove_edge(in_edges[0]);
587 let _ = self.remove_edge(out_edges[0]);
588 let _ = self.remove_node(node_id);
589 let _ = self.add_edge(source, target, new_weight);
590 contracted += 1;
591 } else {
592 let adj_edges = self.outgoing_edges(node_id).to_vec();
593 if adj_edges.len() != 2 {
594 break;
595 }
596
597 let e0 = match self.get_edge(adj_edges[0]) {
598 Some(e) => e.clone(),
599 None => break,
600 };
601 let e1 = match self.get_edge(adj_edges[1]) {
602 Some(e) => e.clone(),
603 None => break,
604 };
605
606 let n0 = e0.other_node(node_id).unwrap_or(node_id);
607 let n1 = e1.other_node(node_id).unwrap_or(node_id);
608 let new_weight = e0.weight + e1.weight;
609
610 let _ = self.remove_edge(adj_edges[0]);
611 let _ = self.remove_edge(adj_edges[1]);
612 let _ = self.remove_node(node_id);
613 let _ = self.add_edge(n0, n1, new_weight);
614 contracted += 1;
615 }
616 }
617
618 contracted
619 }
620
621 pub fn snap_nodes(&mut self, tolerance: f64) -> usize {
623 let node_ids: Vec<NodeId> = self.node_ids();
624 let mut merge_map: HashMap<NodeId, NodeId> = HashMap::new();
625 let mut merged_count = 0;
626
627 for i in 0..node_ids.len() {
628 if merge_map.contains_key(&node_ids[i]) {
629 continue;
630 }
631
632 let coord_i = match self.get_node(node_ids[i]) {
633 Some(n) => n.coordinate,
634 None => continue,
635 };
636
637 for j in (i + 1)..node_ids.len() {
638 if merge_map.contains_key(&node_ids[j]) {
639 continue;
640 }
641
642 let coord_j = match self.get_node(node_ids[j]) {
643 Some(n) => n.coordinate,
644 None => continue,
645 };
646
647 let dx = coord_i.x - coord_j.x;
648 let dy = coord_i.y - coord_j.y;
649 let dist = (dx * dx + dy * dy).sqrt();
650 if dist < tolerance {
651 merge_map.insert(node_ids[j], node_ids[i]);
652 merged_count += 1;
653 }
654 }
655 }
656
657 let edge_ids: Vec<EdgeId> = self.edge_ids();
659 for edge_id in edge_ids {
660 let edge = match self.get_edge(edge_id) {
661 Some(e) => e.clone(),
662 None => continue,
663 };
664
665 let new_source = merge_map.get(&edge.source).copied().unwrap_or(edge.source);
666 let new_target = merge_map.get(&edge.target).copied().unwrap_or(edge.target);
667
668 if new_source != edge.source || new_target != edge.target {
669 let _ = self.remove_edge(edge_id);
670 if new_source != new_target {
671 let _ = self.add_edge(new_source, new_target, edge.weight);
672 }
673 }
674 }
675
676 for old_id in merge_map.keys() {
678 self.remove_node_unchecked(*old_id);
679 }
680
681 merged_count
682 }
683
684 pub fn clean_topology(&mut self, tolerance: f64) -> TopologyCleanResult {
686 let snapped = self.snap_nodes(tolerance);
687 let self_loops = self.remove_self_loops();
688 let parallel = self.remove_parallel_edges();
689 let isolated = self.remove_isolated_nodes();
690 let contracted = self.contract_degree2_nodes();
691
692 TopologyCleanResult {
693 nodes_snapped: snapped,
694 self_loops_removed: self_loops.len(),
695 parallel_edges_removed: parallel.len(),
696 isolated_nodes_removed: isolated.len(),
697 degree2_nodes_contracted: contracted,
698 }
699 }
700}
701
702fn tarjan_dfs(
704 graph: &Graph,
705 v: NodeId,
706 index_counter: &mut usize,
707 stack: &mut Vec<NodeId>,
708 on_stack: &mut HashSet<NodeId>,
709 index_map: &mut HashMap<NodeId, usize>,
710 lowlink: &mut HashMap<NodeId, usize>,
711 sccs: &mut Vec<Vec<NodeId>>,
712) {
713 index_map.insert(v, *index_counter);
714 lowlink.insert(v, *index_counter);
715 *index_counter += 1;
716 stack.push(v);
717 on_stack.insert(v);
718
719 for &edge_id in graph.outgoing_edges(v) {
720 if let Some(edge) = graph.get_edge(edge_id) {
721 let w = edge.target;
722 if !index_map.contains_key(&w) {
723 tarjan_dfs(
724 graph,
725 w,
726 index_counter,
727 stack,
728 on_stack,
729 index_map,
730 lowlink,
731 sccs,
732 );
733 let w_low = lowlink.get(&w).copied().unwrap_or(0);
734 let v_low = lowlink.get(&v).copied().unwrap_or(0);
735 lowlink.insert(v, v_low.min(w_low));
736 } else if on_stack.contains(&w) {
737 let w_idx = index_map.get(&w).copied().unwrap_or(0);
738 let v_low = lowlink.get(&v).copied().unwrap_or(0);
739 lowlink.insert(v, v_low.min(w_idx));
740 }
741 }
742 }
743
744 let v_low = lowlink.get(&v).copied().unwrap_or(0);
745 let v_idx = index_map.get(&v).copied().unwrap_or(0);
746 if v_low == v_idx {
747 let mut scc = Vec::new();
748 while let Some(w) = stack.pop() {
749 on_stack.remove(&w);
750 scc.push(w);
751 if w == v {
752 break;
753 }
754 }
755 sccs.push(scc);
756 }
757}
758
759#[cfg(test)]
760mod tests {
761 use super::*;
762 use crate::vector::network::{EdgeWeight, GraphBuilder, GraphType};
763 use oxigdal_core::vector::LineString;
764
765 #[test]
766 fn test_validate_graph() {
767 let mut graph = Graph::new();
768 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
769 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
770 let _ = graph.add_edge(n1, n2, 1.0);
771 assert!(graph.validate().is_ok());
772 }
773
774 #[test]
775 fn test_validate_detailed() {
776 let mut graph = Graph::new();
777 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
778 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
779 let _ = graph.add_node(Coordinate::new_2d(5.0, 5.0)); let _ = graph.add_edge(n1, n2, 1.0);
781 let _ = graph.add_edge(n1, n1, 0.5); let result = graph.validate_detailed();
784 assert!(result.is_valid);
785 assert!(!result.issues.is_empty());
786 }
787
788 #[test]
789 fn test_connected_components() {
790 let mut graph = Graph::new();
791 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
792 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
793 let n3 = graph.add_node(Coordinate::new_2d(5.0, 5.0));
794 let n4 = graph.add_node(Coordinate::new_2d(6.0, 5.0));
795 let _ = graph.add_edge(n1, n2, 1.0);
796 let _ = graph.add_edge(n3, n4, 1.0);
797 let components = graph.connected_components();
798 assert_eq!(components.len(), 2);
799 }
800
801 #[test]
802 fn test_strongly_connected_components() {
803 let mut graph = Graph::with_type(GraphType::Directed);
804 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
805 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
806 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
807 let _ = graph.add_edge(n0, n1, 1.0);
808 let _ = graph.add_edge(n1, n2, 1.0);
809 let _ = graph.add_edge(n2, n0, 1.0);
810 let sccs = graph.strongly_connected_components();
811 assert_eq!(sccs.len(), 1);
812 assert_eq!(sccs[0].len(), 3);
813 }
814
815 #[test]
816 fn test_remove_self_loops() {
817 let mut graph = Graph::new();
818 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
819 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
820 let _ = graph.add_edge(n1, n1, 0.5);
821 let _ = graph.add_edge(n1, n2, 1.0);
822 assert_eq!(graph.num_edges(), 2);
823 let removed = graph.remove_self_loops();
824 assert_eq!(removed.len(), 1);
825 assert_eq!(graph.num_edges(), 1);
826 }
827
828 #[test]
829 fn test_remove_isolated_nodes() {
830 let mut graph = Graph::new();
831 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
832 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
833 let _ = graph.add_node(Coordinate::new_2d(5.0, 5.0));
834 let _ = graph.add_edge(n1, n2, 1.0);
835 assert_eq!(graph.num_nodes(), 3);
836 let removed = graph.remove_isolated_nodes();
837 assert_eq!(removed.len(), 1);
838 assert_eq!(graph.num_nodes(), 2);
839 }
840
841 #[test]
842 fn test_clean_topology() {
843 let mut graph = Graph::new();
844 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
845 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
846 let _ = graph.add_node(Coordinate::new_2d(100.0, 100.0));
847 let _ = graph.add_edge(n1, n1, 0.5);
848 let _ = graph.add_edge(n1, n2, 1.0);
849 let _ = graph.add_edge(n1, n2, 2.0);
850 let result = graph.clean_topology(0.0);
851 assert_eq!(result.self_loops_removed, 1);
852 assert_eq!(result.parallel_edges_removed, 1);
853 assert_eq!(result.isolated_nodes_removed, 1);
854 }
855
856 #[test]
857 fn test_time_dependent_weight() {
858 let tdw = TimeDependentWeight::rush_hour();
859 assert!((tdw.multiplier_at(3600.0) - 0.8).abs() < 1e-10);
860 assert!((tdw.multiplier_at(28800.0) - 1.5).abs() < 1e-10);
861 assert!((tdw.multiplier_at(43200.0) - 1.0).abs() < 1e-10);
862 }
863
864 #[test]
865 fn test_turn_penalties() {
866 let mut tp = TurnPenalties::new();
867 let node = NodeId(1);
868 let from = EdgeId(0);
869 let to = EdgeId(2);
870 tp.add_penalty(node, from, to, 5.0);
871 assert!((tp.get_penalty(node, from, to) - 5.0).abs() < 1e-10);
872 assert!((tp.get_penalty(node, from, EdgeId(3)) - 0.0).abs() < 1e-10);
873 }
874
875 #[test]
876 fn test_turn_prohibition() {
877 let mut tp = TurnPenalties::new();
878 let node = NodeId(1);
879 let from = EdgeId(0);
880 let to = EdgeId(2);
881 tp.add_prohibition(node, from, to);
882 assert!(tp.is_prohibited(node, from, to));
883 assert!(tp.get_penalty(node, from, to).is_infinite());
884 }
885
886 #[test]
887 fn test_haversine_distance() {
888 let london = Coordinate::new_2d(-0.1278, 51.5074);
889 let paris = Coordinate::new_2d(2.3522, 48.8566);
890 let dist = haversine_distance(&london, &paris);
891 assert!(dist > 300_000.0 && dist < 400_000.0);
892 }
893}