1use crate::error::{AlgorithmError, Result};
14use crate::vector::network::{EdgeId, Graph, NodeId, TimeDependentWeight, TurnPenalties};
15use std::cmp::Ordering;
16use std::collections::{BinaryHeap, HashMap};
17
18type EdgePredecessorMap = HashMap<(NodeId, Option<EdgeId>), (NodeId, Option<EdgeId>, EdgeId)>;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum PathFindingAlgorithm {
25 Dijkstra,
27 AStar,
29 Bidirectional,
31 DijkstraTurnRestricted,
33 AStarTurnRestricted,
35 TimeDependentDijkstra,
37}
38
39#[derive(Debug, Clone)]
41pub struct ShortestPathOptions {
42 pub algorithm: PathFindingAlgorithm,
44 pub max_length: Option<f64>,
46 pub include_geometry: bool,
48 pub heuristic_weight: f64,
50 pub turn_penalties: Option<TurnPenalties>,
52 pub time_dependent_weights: Option<HashMap<EdgeId, TimeDependentWeight>>,
54 pub departure_time: f64,
56 pub weight_criteria: Option<HashMap<String, f64>>,
58}
59
60impl Default for ShortestPathOptions {
61 fn default() -> Self {
62 Self {
63 algorithm: PathFindingAlgorithm::Dijkstra,
64 max_length: None,
65 include_geometry: true,
66 heuristic_weight: 1.0,
67 turn_penalties: None,
68 time_dependent_weights: None,
69 departure_time: 0.0,
70 weight_criteria: None,
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct ShortestPath {
78 pub nodes: Vec<NodeId>,
80 pub edges: Vec<EdgeId>,
82 pub cost: f64,
84 pub found: bool,
86 pub nodes_visited: usize,
88}
89
90impl ShortestPath {
91 pub fn not_found() -> Self {
93 Self {
94 nodes: Vec::new(),
95 edges: Vec::new(),
96 cost: f64::INFINITY,
97 found: false,
98 nodes_visited: 0,
99 }
100 }
101
102 pub fn new(nodes: Vec<NodeId>, edges: Vec<EdgeId>, cost: f64) -> Self {
104 Self {
105 nodes,
106 edges,
107 cost,
108 found: true,
109 nodes_visited: 0,
110 }
111 }
112
113 pub fn with_visited(mut self, count: usize) -> Self {
115 self.nodes_visited = count;
116 self
117 }
118}
119
120#[derive(Debug, Clone)]
122struct DijkstraState {
123 cost: f64,
124 node: NodeId,
125}
126
127impl PartialEq for DijkstraState {
128 fn eq(&self, other: &Self) -> bool {
129 self.cost == other.cost && self.node == other.node
130 }
131}
132
133impl Eq for DijkstraState {}
134
135impl PartialOrd for DijkstraState {
136 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
137 Some(self.cmp(other))
138 }
139}
140
141impl Ord for DijkstraState {
142 fn cmp(&self, other: &Self) -> Ordering {
143 other
145 .cost
146 .partial_cmp(&self.cost)
147 .unwrap_or(Ordering::Equal)
148 }
149}
150
151#[derive(Debug, Clone)]
153struct EdgeState {
154 cost: f64,
155 node: NodeId,
156 arriving_edge: Option<EdgeId>,
158}
159
160impl PartialEq for EdgeState {
161 fn eq(&self, other: &Self) -> bool {
162 self.cost == other.cost && self.node == other.node
163 }
164}
165
166impl Eq for EdgeState {}
167
168impl PartialOrd for EdgeState {
169 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
170 Some(self.cmp(other))
171 }
172}
173
174impl Ord for EdgeState {
175 fn cmp(&self, other: &Self) -> Ordering {
176 other
177 .cost
178 .partial_cmp(&self.cost)
179 .unwrap_or(Ordering::Equal)
180 }
181}
182
183pub fn dijkstra_search(
196 graph: &Graph,
197 start: NodeId,
198 end: NodeId,
199 options: &ShortestPathOptions,
200) -> Result<ShortestPath> {
201 if graph.get_node(start).is_none() {
202 return Err(AlgorithmError::InvalidGeometry(format!(
203 "Start node {:?} not found",
204 start
205 )));
206 }
207
208 if graph.get_node(end).is_none() {
209 return Err(AlgorithmError::InvalidGeometry(format!(
210 "End node {:?} not found",
211 end
212 )));
213 }
214
215 let mut distances: HashMap<NodeId, f64> = HashMap::new();
216 let mut predecessors: HashMap<NodeId, (NodeId, EdgeId)> = HashMap::new();
217 let mut heap = BinaryHeap::new();
218 let mut visited_count: usize = 0;
219
220 distances.insert(start, 0.0);
221 heap.push(DijkstraState {
222 cost: 0.0,
223 node: start,
224 });
225
226 while let Some(DijkstraState { cost, node }) = heap.pop() {
227 visited_count += 1;
228
229 if let Some(&best_cost) = distances.get(&node) {
231 if cost > best_cost {
232 continue;
233 }
234 }
235
236 if let Some(max_len) = options.max_length {
238 if cost > max_len {
239 continue;
240 }
241 }
242
243 if node == end {
245 let path = reconstruct_path(start, end, &predecessors, cost);
246 return Ok(path.with_visited(visited_count));
247 }
248
249 for &edge_id in graph.outgoing_edges(node) {
251 if let Some(edge) = graph.get_edge(edge_id) {
252 let next_node = edge.target;
253 let edge_cost = get_edge_cost(edge, options);
254 let next_cost = cost + edge_cost;
255
256 let is_better = distances
257 .get(&next_node)
258 .is_none_or(|¤t| next_cost < current);
259
260 if is_better {
261 distances.insert(next_node, next_cost);
262 predecessors.insert(next_node, (node, edge_id));
263 heap.push(DijkstraState {
264 cost: next_cost,
265 node: next_node,
266 });
267 }
268 }
269 }
270 }
271
272 Ok(ShortestPath::not_found())
273}
274
275pub fn dijkstra_turn_restricted(
279 graph: &Graph,
280 start: NodeId,
281 end: NodeId,
282 options: &ShortestPathOptions,
283) -> Result<ShortestPath> {
284 if graph.get_node(start).is_none() {
285 return Err(AlgorithmError::InvalidGeometry(format!(
286 "Start node {:?} not found",
287 start
288 )));
289 }
290 if graph.get_node(end).is_none() {
291 return Err(AlgorithmError::InvalidGeometry(format!(
292 "End node {:?} not found",
293 end
294 )));
295 }
296
297 let turn_penalties = options.turn_penalties.as_ref().cloned().unwrap_or_default();
298
299 let mut distances: HashMap<(NodeId, Option<EdgeId>), f64> = HashMap::new();
301 let mut predecessors: EdgePredecessorMap = HashMap::new();
302 let mut heap = BinaryHeap::new();
303 let mut visited_count: usize = 0;
304
305 distances.insert((start, None), 0.0);
306 heap.push(EdgeState {
307 cost: 0.0,
308 node: start,
309 arriving_edge: None,
310 });
311
312 let mut best_end_cost = f64::INFINITY;
313 let mut best_end_arriving: Option<EdgeId> = None;
314
315 while let Some(EdgeState {
316 cost,
317 node,
318 arriving_edge,
319 }) = heap.pop()
320 {
321 visited_count += 1;
322
323 if node == end && cost < best_end_cost {
324 best_end_cost = cost;
325 best_end_arriving = arriving_edge;
326 }
327
328 if cost > best_end_cost {
329 break; }
331
332 let state_key = (node, arriving_edge);
333 if let Some(&best) = distances.get(&state_key) {
334 if cost > best {
335 continue;
336 }
337 }
338
339 if let Some(max_len) = options.max_length {
340 if cost > max_len {
341 continue;
342 }
343 }
344
345 for &edge_id in graph.outgoing_edges(node) {
346 if let Some(edge) = graph.get_edge(edge_id) {
347 let next_node = edge.target;
348
349 let turn_cost = if let Some(from_edge) = arriving_edge {
351 let penalty = turn_penalties.get_penalty(node, from_edge, edge_id);
352 if penalty.is_infinite() {
353 continue; }
355 penalty
356 } else {
357 0.0
358 };
359
360 let edge_cost = get_edge_cost(edge, options);
361 let next_cost = cost + edge_cost + turn_cost;
362
363 let next_key = (next_node, Some(edge_id));
364 let is_better = distances
365 .get(&next_key)
366 .is_none_or(|¤t| next_cost < current);
367
368 if is_better {
369 distances.insert(next_key, next_cost);
370 predecessors.insert(next_key, (node, arriving_edge, edge_id));
371 heap.push(EdgeState {
372 cost: next_cost,
373 node: next_node,
374 arriving_edge: Some(edge_id),
375 });
376 }
377 }
378 }
379 }
380
381 if best_end_cost < f64::INFINITY {
382 let path = reconstruct_edge_based_path(
384 start,
385 end,
386 best_end_arriving,
387 &predecessors,
388 best_end_cost,
389 );
390 Ok(path.with_visited(visited_count))
391 } else {
392 Ok(ShortestPath::not_found())
393 }
394}
395
396fn reconstruct_edge_based_path(
398 start: NodeId,
399 end: NodeId,
400 end_arriving: Option<EdgeId>,
401 predecessors: &EdgePredecessorMap,
402 cost: f64,
403) -> ShortestPath {
404 let mut nodes = Vec::new();
405 let mut edges = Vec::new();
406
407 let mut current_node = end;
408 let mut current_arriving = end_arriving;
409
410 nodes.push(current_node);
411
412 while current_node != start || current_arriving.is_some() {
413 let key = (current_node, current_arriving);
414 if let Some(&(prev_node, prev_arriving, edge_id)) = predecessors.get(&key) {
415 edges.push(edge_id);
416 nodes.push(prev_node);
417 current_node = prev_node;
418 current_arriving = prev_arriving;
419 } else {
420 if current_node == start {
421 break;
422 }
423 return ShortestPath::not_found();
424 }
425 }
426
427 nodes.reverse();
428 edges.reverse();
429
430 ShortestPath::new(nodes, edges, cost)
431}
432
433fn get_edge_cost(edge: &crate::vector::network::Edge, options: &ShortestPathOptions) -> f64 {
435 if let Some(ref criteria) = options.weight_criteria {
436 edge.multi_weight.weighted_cost(criteria)
437 } else {
438 edge.weight
439 }
440}
441
442fn reconstruct_path(
444 start: NodeId,
445 end: NodeId,
446 predecessors: &HashMap<NodeId, (NodeId, EdgeId)>,
447 cost: f64,
448) -> ShortestPath {
449 let mut nodes = Vec::new();
450 let mut edges = Vec::new();
451 let mut current = end;
452
453 nodes.push(current);
454
455 while current != start {
456 if let Some(&(prev_node, edge_id)) = predecessors.get(¤t) {
457 edges.push(edge_id);
458 nodes.push(prev_node);
459 current = prev_node;
460 } else {
461 return ShortestPath::not_found();
463 }
464 }
465
466 nodes.reverse();
467 edges.reverse();
468
469 ShortestPath::new(nodes, edges, cost)
470}
471
472pub fn astar_search(
476 graph: &Graph,
477 start: NodeId,
478 end: NodeId,
479 options: &ShortestPathOptions,
480) -> Result<ShortestPath> {
481 if graph.get_node(start).is_none() {
482 return Err(AlgorithmError::InvalidGeometry(format!(
483 "Start node {:?} not found",
484 start
485 )));
486 }
487
488 let end_node = graph
489 .get_node(end)
490 .ok_or_else(|| AlgorithmError::InvalidGeometry(format!("End node {:?} not found", end)))?;
491
492 let end_coord = &end_node.coordinate;
493
494 let mut g_scores: HashMap<NodeId, f64> = HashMap::new();
495 let mut predecessors: HashMap<NodeId, (NodeId, EdgeId)> = HashMap::new();
496 let mut heap = BinaryHeap::new();
497 let mut visited_count: usize = 0;
498
499 g_scores.insert(start, 0.0);
500 let h_start = heuristic(graph, start, end_coord) * options.heuristic_weight;
501
502 heap.push(DijkstraState {
503 cost: h_start,
504 node: start,
505 });
506
507 while let Some(DijkstraState { cost: _f, node }) = heap.pop() {
508 visited_count += 1;
509
510 let current_g = g_scores.get(&node).copied().unwrap_or(f64::INFINITY);
511
512 if let Some(max_len) = options.max_length {
514 if current_g > max_len {
515 continue;
516 }
517 }
518
519 if node == end {
520 let cost = g_scores.get(&end).copied().unwrap_or(f64::INFINITY);
521 let path = reconstruct_path(start, end, &predecessors, cost);
522 return Ok(path.with_visited(visited_count));
523 }
524
525 for &edge_id in graph.outgoing_edges(node) {
526 if let Some(edge) = graph.get_edge(edge_id) {
527 let next_node = edge.target;
528 let edge_cost = get_edge_cost(edge, options);
529 let tentative_g = current_g + edge_cost;
530
531 let is_better = g_scores
532 .get(&next_node)
533 .is_none_or(|¤t| tentative_g < current);
534
535 if is_better {
536 g_scores.insert(next_node, tentative_g);
537 predecessors.insert(next_node, (node, edge_id));
538
539 let h = heuristic(graph, next_node, end_coord) * options.heuristic_weight;
540 let f = tentative_g + h;
541
542 heap.push(DijkstraState {
543 cost: f,
544 node: next_node,
545 });
546 }
547 }
548 }
549 }
550
551 Ok(ShortestPath::not_found())
552}
553
554pub fn astar_turn_restricted(
556 graph: &Graph,
557 start: NodeId,
558 end: NodeId,
559 options: &ShortestPathOptions,
560) -> Result<ShortestPath> {
561 if graph.get_node(start).is_none() {
562 return Err(AlgorithmError::InvalidGeometry(format!(
563 "Start node {:?} not found",
564 start
565 )));
566 }
567
568 let end_node = graph
569 .get_node(end)
570 .ok_or_else(|| AlgorithmError::InvalidGeometry(format!("End node {:?} not found", end)))?;
571 let end_coord = end_node.coordinate;
572
573 let turn_penalties = options.turn_penalties.as_ref().cloned().unwrap_or_default();
574
575 let mut g_scores: HashMap<(NodeId, Option<EdgeId>), f64> = HashMap::new();
577 let mut predecessors: EdgePredecessorMap = HashMap::new();
578 let mut heap = BinaryHeap::new();
579 let mut visited_count: usize = 0;
580
581 g_scores.insert((start, None), 0.0);
582 let h_start = heuristic(graph, start, &end_coord) * options.heuristic_weight;
583 heap.push(EdgeState {
584 cost: h_start,
585 node: start,
586 arriving_edge: None,
587 });
588
589 let mut best_end_cost = f64::INFINITY;
590 let mut best_end_arriving: Option<EdgeId> = None;
591
592 while let Some(EdgeState {
593 cost: _f,
594 node,
595 arriving_edge,
596 }) = heap.pop()
597 {
598 visited_count += 1;
599
600 let state_key = (node, arriving_edge);
601 let current_g = g_scores.get(&state_key).copied().unwrap_or(f64::INFINITY);
602
603 if node == end && current_g < best_end_cost {
604 best_end_cost = current_g;
605 best_end_arriving = arriving_edge;
606 }
607
608 if current_g > best_end_cost {
609 break;
610 }
611
612 if let Some(max_len) = options.max_length {
613 if current_g > max_len {
614 continue;
615 }
616 }
617
618 for &edge_id in graph.outgoing_edges(node) {
619 if let Some(edge) = graph.get_edge(edge_id) {
620 let next_node = edge.target;
621
622 let turn_cost = if let Some(from_edge) = arriving_edge {
623 let penalty = turn_penalties.get_penalty(node, from_edge, edge_id);
624 if penalty.is_infinite() {
625 continue;
626 }
627 penalty
628 } else {
629 0.0
630 };
631
632 let edge_cost = get_edge_cost(edge, options);
633 let tentative_g = current_g + edge_cost + turn_cost;
634
635 let next_key = (next_node, Some(edge_id));
636 let is_better = g_scores
637 .get(&next_key)
638 .is_none_or(|¤t| tentative_g < current);
639
640 if is_better {
641 g_scores.insert(next_key, tentative_g);
642 predecessors.insert(next_key, (node, arriving_edge, edge_id));
643
644 let h = heuristic(graph, next_node, &end_coord) * options.heuristic_weight;
645 let f = tentative_g + h;
646
647 heap.push(EdgeState {
648 cost: f,
649 node: next_node,
650 arriving_edge: Some(edge_id),
651 });
652 }
653 }
654 }
655 }
656
657 if best_end_cost < f64::INFINITY {
658 let path = reconstruct_edge_based_path(
659 start,
660 end,
661 best_end_arriving,
662 &predecessors,
663 best_end_cost,
664 );
665 Ok(path.with_visited(visited_count))
666 } else {
667 Ok(ShortestPath::not_found())
668 }
669}
670
671fn heuristic(graph: &Graph, node: NodeId, target_coord: &oxigdal_core::vector::Coordinate) -> f64 {
673 if let Some(current_node) = graph.get_node(node) {
674 let dx = current_node.coordinate.x - target_coord.x;
675 let dy = current_node.coordinate.y - target_coord.y;
676 (dx * dx + dy * dy).sqrt()
677 } else {
678 0.0
679 }
680}
681
682pub fn bidirectional_search(
687 graph: &Graph,
688 start: NodeId,
689 end: NodeId,
690 options: &ShortestPathOptions,
691) -> Result<ShortestPath> {
692 if graph.get_node(start).is_none() {
693 return Err(AlgorithmError::InvalidGeometry(format!(
694 "Start node {:?} not found",
695 start
696 )));
697 }
698
699 if graph.get_node(end).is_none() {
700 return Err(AlgorithmError::InvalidGeometry(format!(
701 "End node {:?} not found",
702 end
703 )));
704 }
705
706 if start == end {
708 return Ok(ShortestPath::new(vec![start], vec![], 0.0));
709 }
710
711 let mut forward_distances: HashMap<NodeId, f64> = HashMap::new();
713 let mut forward_predecessors: HashMap<NodeId, (NodeId, EdgeId)> = HashMap::new();
714 let mut forward_heap = BinaryHeap::new();
715 let mut forward_settled: HashMap<NodeId, f64> = HashMap::new();
716
717 let mut backward_distances: HashMap<NodeId, f64> = HashMap::new();
719 let mut backward_predecessors: HashMap<NodeId, (NodeId, EdgeId)> = HashMap::new();
720 let mut backward_heap = BinaryHeap::new();
721 let mut backward_settled: HashMap<NodeId, f64> = HashMap::new();
722
723 let mut visited_count: usize = 0;
724
725 forward_distances.insert(start, 0.0);
726 forward_heap.push(DijkstraState {
727 cost: 0.0,
728 node: start,
729 });
730
731 backward_distances.insert(end, 0.0);
732 backward_heap.push(DijkstraState {
733 cost: 0.0,
734 node: end,
735 });
736
737 let mut best_cost = f64::INFINITY;
738 let mut meeting_node: Option<NodeId> = None;
739
740 loop {
742 let forward_min = forward_heap.peek().map(|s| s.cost).unwrap_or(f64::INFINITY);
743 let backward_min = backward_heap
744 .peek()
745 .map(|s| s.cost)
746 .unwrap_or(f64::INFINITY);
747
748 if forward_min + backward_min >= best_cost {
750 break;
751 }
752
753 if forward_heap.is_empty() && backward_heap.is_empty() {
754 break;
755 }
756
757 if forward_min <= backward_min {
759 if let Some(DijkstraState { cost, node }) = forward_heap.pop() {
760 visited_count += 1;
761
762 if let Some(&best) = forward_distances.get(&node) {
763 if cost > best {
764 continue;
765 }
766 }
767
768 forward_settled.insert(node, cost);
769
770 if let Some(&backward_cost) = backward_settled.get(&node) {
772 let total = cost + backward_cost;
773 if total < best_cost {
774 best_cost = total;
775 meeting_node = Some(node);
776 }
777 }
778
779 if let Some(max_len) = options.max_length {
780 if cost > max_len {
781 continue;
782 }
783 }
784
785 for &edge_id in graph.outgoing_edges(node) {
786 if let Some(edge) = graph.get_edge(edge_id) {
787 let next_node = edge.target;
788 let edge_cost = get_edge_cost(edge, options);
789 let next_cost = cost + edge_cost;
790
791 let is_better = forward_distances
792 .get(&next_node)
793 .is_none_or(|¤t| next_cost < current);
794
795 if is_better {
796 forward_distances.insert(next_node, next_cost);
797 forward_predecessors.insert(next_node, (node, edge_id));
798 forward_heap.push(DijkstraState {
799 cost: next_cost,
800 node: next_node,
801 });
802
803 if let Some(&bw_cost) = backward_settled.get(&next_node) {
805 let total = next_cost + bw_cost;
806 if total < best_cost {
807 best_cost = total;
808 meeting_node = Some(next_node);
809 }
810 }
811 }
812 }
813 }
814 }
815 } else {
816 if let Some(DijkstraState { cost, node }) = backward_heap.pop() {
818 visited_count += 1;
819
820 if let Some(&best) = backward_distances.get(&node) {
821 if cost > best {
822 continue;
823 }
824 }
825
826 backward_settled.insert(node, cost);
827
828 if let Some(&forward_cost) = forward_settled.get(&node) {
830 let total = forward_cost + cost;
831 if total < best_cost {
832 best_cost = total;
833 meeting_node = Some(node);
834 }
835 }
836
837 if let Some(max_len) = options.max_length {
838 if cost > max_len {
839 continue;
840 }
841 }
842
843 for &edge_id in graph.incoming_edges(node) {
844 if let Some(edge) = graph.get_edge(edge_id) {
845 let prev_node = edge.source;
846 let edge_cost = get_edge_cost(edge, options);
847 let prev_cost = cost + edge_cost;
848
849 let is_better = backward_distances
850 .get(&prev_node)
851 .is_none_or(|¤t| prev_cost < current);
852
853 if is_better {
854 backward_distances.insert(prev_node, prev_cost);
855 backward_predecessors.insert(prev_node, (node, edge_id));
856 backward_heap.push(DijkstraState {
857 cost: prev_cost,
858 node: prev_node,
859 });
860
861 if let Some(&fw_cost) = forward_settled.get(&prev_node) {
863 let total = fw_cost + prev_cost;
864 if total < best_cost {
865 best_cost = total;
866 meeting_node = Some(prev_node);
867 }
868 }
869 }
870 }
871 }
872 }
873 }
874 }
875
876 if let Some(meeting) = meeting_node {
877 let path = reconstruct_bidirectional_path(
878 start,
879 end,
880 meeting,
881 &forward_predecessors,
882 &backward_predecessors,
883 best_cost,
884 );
885 Ok(path.with_visited(visited_count))
886 } else {
887 Ok(ShortestPath::not_found())
888 }
889}
890
891fn reconstruct_bidirectional_path(
893 start: NodeId,
894 end: NodeId,
895 meeting: NodeId,
896 forward_preds: &HashMap<NodeId, (NodeId, EdgeId)>,
897 backward_preds: &HashMap<NodeId, (NodeId, EdgeId)>,
898 cost: f64,
899) -> ShortestPath {
900 let mut nodes = Vec::new();
901 let mut edges = Vec::new();
902
903 let mut current = meeting;
905 let mut forward_nodes = vec![current];
906 let mut forward_edges = Vec::new();
907
908 while current != start {
909 if let Some(&(prev_node, edge_id)) = forward_preds.get(¤t) {
910 forward_edges.push(edge_id);
911 forward_nodes.push(prev_node);
912 current = prev_node;
913 } else {
914 break;
915 }
916 }
917
918 forward_nodes.reverse();
919 forward_edges.reverse();
920
921 nodes.extend(forward_nodes);
922 edges.extend(forward_edges);
923
924 current = meeting;
926 while current != end {
927 if let Some(&(next_node, edge_id)) = backward_preds.get(¤t) {
928 edges.push(edge_id);
929 nodes.push(next_node);
930 current = next_node;
931 } else {
932 break;
933 }
934 }
935
936 ShortestPath::new(nodes, edges, cost)
937}
938
939pub fn time_dependent_dijkstra(
946 graph: &Graph,
947 start: NodeId,
948 end: NodeId,
949 options: &ShortestPathOptions,
950) -> Result<ShortestPath> {
951 if graph.get_node(start).is_none() {
952 return Err(AlgorithmError::InvalidGeometry(format!(
953 "Start node {:?} not found",
954 start
955 )));
956 }
957 if graph.get_node(end).is_none() {
958 return Err(AlgorithmError::InvalidGeometry(format!(
959 "End node {:?} not found",
960 end
961 )));
962 }
963
964 let td_weights = options
965 .time_dependent_weights
966 .as_ref()
967 .cloned()
968 .unwrap_or_default();
969
970 let departure_time = options.departure_time;
971
972 let mut arrival_times: HashMap<NodeId, f64> = HashMap::new();
974 let mut predecessors: HashMap<NodeId, (NodeId, EdgeId)> = HashMap::new();
975 let mut heap = BinaryHeap::new();
976 let mut visited_count: usize = 0;
977
978 arrival_times.insert(start, departure_time);
979 heap.push(DijkstraState {
980 cost: departure_time,
981 node: start,
982 });
983
984 while let Some(DijkstraState {
985 cost: current_time,
986 node,
987 }) = heap.pop()
988 {
989 visited_count += 1;
990
991 if let Some(&best_time) = arrival_times.get(&node) {
992 if current_time > best_time {
993 continue;
994 }
995 }
996
997 if let Some(max_len) = options.max_length {
998 if current_time - departure_time > max_len {
999 continue;
1000 }
1001 }
1002
1003 if node == end {
1004 let total_cost = current_time - departure_time;
1005 let path = reconstruct_path(start, end, &predecessors, total_cost);
1006 return Ok(path.with_visited(visited_count));
1007 }
1008
1009 for &edge_id in graph.outgoing_edges(node) {
1010 if let Some(edge) = graph.get_edge(edge_id) {
1011 let next_node = edge.target;
1012
1013 let multiplier = td_weights
1015 .get(&edge_id)
1016 .map(|tdw| tdw.multiplier_at(current_time))
1017 .unwrap_or(1.0);
1018
1019 let travel_cost = edge.weight * multiplier;
1020 let arrival_time = current_time + travel_cost;
1021
1022 let is_better = arrival_times
1023 .get(&next_node)
1024 .is_none_or(|¤t| arrival_time < current);
1025
1026 if is_better {
1027 arrival_times.insert(next_node, arrival_time);
1028 predecessors.insert(next_node, (node, edge_id));
1029 heap.push(DijkstraState {
1030 cost: arrival_time,
1031 node: next_node,
1032 });
1033 }
1034 }
1035 }
1036 }
1037
1038 Ok(ShortestPath::not_found())
1039}
1040
1041#[derive(Debug, Clone)]
1043pub struct AllPairsResult {
1044 pub distances: HashMap<NodeId, HashMap<NodeId, f64>>,
1046 pub next_hop: HashMap<NodeId, HashMap<NodeId, Option<NodeId>>>,
1048 pub node_order: Vec<NodeId>,
1050}
1051
1052impl AllPairsResult {
1053 pub fn distance(&self, from: NodeId, to: NodeId) -> f64 {
1055 self.distances
1056 .get(&from)
1057 .and_then(|row| row.get(&to))
1058 .copied()
1059 .unwrap_or(f64::INFINITY)
1060 }
1061
1062 pub fn path(&self, from: NodeId, to: NodeId) -> Option<Vec<NodeId>> {
1064 if self.distance(from, to).is_infinite() {
1065 return None;
1066 }
1067
1068 let mut path = vec![from];
1069 let mut current = from;
1070
1071 while current != to {
1072 let next = self
1073 .next_hop
1074 .get(¤t)
1075 .and_then(|row| row.get(&to))
1076 .copied()
1077 .flatten();
1078
1079 match next {
1080 Some(n) => {
1081 if path.contains(&n) {
1082 return None; }
1084 path.push(n);
1085 current = n;
1086 }
1087 None => return None,
1088 }
1089 }
1090
1091 Some(path)
1092 }
1093}
1094
1095pub fn floyd_warshall(graph: &Graph, max_nodes: usize) -> Result<AllPairsResult> {
1109 let n = graph.num_nodes();
1110 if n > max_nodes {
1111 return Err(AlgorithmError::InvalidParameter {
1112 parameter: "max_nodes",
1113 message: format!(
1114 "Graph has {} nodes, exceeds limit of {} for Floyd-Warshall",
1115 n, max_nodes
1116 ),
1117 });
1118 }
1119
1120 let node_order: Vec<NodeId> = graph.sorted_node_ids();
1121 let node_index: HashMap<NodeId, usize> = node_order
1122 .iter()
1123 .enumerate()
1124 .map(|(i, &id)| (id, i))
1125 .collect();
1126
1127 let mut dist: Vec<Vec<f64>> = vec![vec![f64::INFINITY; n]; n];
1129 let mut next: Vec<Vec<Option<usize>>> = vec![vec![None; n]; n];
1130
1131 for i in 0..n {
1133 dist[i][i] = 0.0;
1134 next[i][i] = Some(i);
1135 }
1136
1137 for edge in graph.edges_iter().map(|(_, e)| e) {
1139 let src_idx = node_index.get(&edge.source).copied();
1140 let tgt_idx = node_index.get(&edge.target).copied();
1141
1142 if let (Some(i), Some(j)) = (src_idx, tgt_idx) {
1143 if edge.weight < dist[i][j] {
1144 dist[i][j] = edge.weight;
1145 next[i][j] = Some(j);
1146 }
1147 }
1148 }
1149
1150 for k in 0..n {
1152 for i in 0..n {
1153 if dist[i][k] == f64::INFINITY {
1154 continue;
1155 }
1156 for j in 0..n {
1157 if dist[k][j] == f64::INFINITY {
1158 continue;
1159 }
1160 let new_dist = dist[i][k] + dist[k][j];
1161 if new_dist < dist[i][j] {
1162 dist[i][j] = new_dist;
1163 next[i][j] = next[i][k];
1164 }
1165 }
1166 }
1167 }
1168
1169 let mut distances: HashMap<NodeId, HashMap<NodeId, f64>> = HashMap::new();
1171 let mut next_hop: HashMap<NodeId, HashMap<NodeId, Option<NodeId>>> = HashMap::new();
1172
1173 for i in 0..n {
1174 let from_id = node_order[i];
1175 let mut dist_row = HashMap::new();
1176 let mut next_row = HashMap::new();
1177
1178 for j in 0..n {
1179 let to_id = node_order[j];
1180 dist_row.insert(to_id, dist[i][j]);
1181 next_row.insert(to_id, next[i][j].map(|idx| node_order[idx]));
1182 }
1183
1184 distances.insert(from_id, dist_row);
1185 next_hop.insert(from_id, next_row);
1186 }
1187
1188 Ok(AllPairsResult {
1189 distances,
1190 next_hop,
1191 node_order,
1192 })
1193}
1194
1195pub fn dijkstra_single_source(
1197 graph: &Graph,
1198 source: NodeId,
1199 options: &ShortestPathOptions,
1200) -> Result<HashMap<NodeId, (f64, Vec<NodeId>)>> {
1201 if graph.get_node(source).is_none() {
1202 return Err(AlgorithmError::InvalidGeometry(format!(
1203 "Source node {:?} not found",
1204 source
1205 )));
1206 }
1207
1208 let mut distances: HashMap<NodeId, f64> = HashMap::new();
1209 let mut predecessors: HashMap<NodeId, NodeId> = HashMap::new();
1210 let mut heap = BinaryHeap::new();
1211
1212 distances.insert(source, 0.0);
1213 heap.push(DijkstraState {
1214 cost: 0.0,
1215 node: source,
1216 });
1217
1218 while let Some(DijkstraState { cost, node }) = heap.pop() {
1219 if let Some(&best) = distances.get(&node) {
1220 if cost > best {
1221 continue;
1222 }
1223 }
1224
1225 if let Some(max_len) = options.max_length {
1226 if cost > max_len {
1227 continue;
1228 }
1229 }
1230
1231 for &edge_id in graph.outgoing_edges(node) {
1232 if let Some(edge) = graph.get_edge(edge_id) {
1233 let next_node = edge.target;
1234 let edge_cost = get_edge_cost(edge, options);
1235 let next_cost = cost + edge_cost;
1236
1237 let is_better = distances
1238 .get(&next_node)
1239 .is_none_or(|¤t| next_cost < current);
1240
1241 if is_better {
1242 distances.insert(next_node, next_cost);
1243 predecessors.insert(next_node, node);
1244 heap.push(DijkstraState {
1245 cost: next_cost,
1246 node: next_node,
1247 });
1248 }
1249 }
1250 }
1251 }
1252
1253 let mut result = HashMap::new();
1255 for (&node, &dist) in &distances {
1256 let mut path = vec![node];
1257 let mut current = node;
1258 while current != source {
1259 if let Some(&prev) = predecessors.get(¤t) {
1260 path.push(prev);
1261 current = prev;
1262 } else {
1263 break;
1264 }
1265 }
1266 path.reverse();
1267 result.insert(node, (dist, path));
1268 }
1269
1270 Ok(result)
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275 use super::*;
1276 use crate::vector::network::TurnPenalties;
1277 use oxigdal_core::vector::Coordinate;
1278
1279 fn create_test_graph() -> Graph {
1280 let mut graph = Graph::new();
1281
1282 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1287 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1288 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1289 let n3 = graph.add_node(Coordinate::new_2d(0.0, 1.0));
1290 let n4 = graph.add_node(Coordinate::new_2d(2.0, 1.0));
1291
1292 let _ = graph.add_edge(n0, n1, 1.0);
1293 let _ = graph.add_edge(n1, n2, 1.0);
1294 let _ = graph.add_edge(n0, n3, 1.0);
1295 let _ = graph.add_edge(n3, n4, 3.0);
1296 let _ = graph.add_edge(n2, n4, 1.0);
1297
1298 graph
1299 }
1300
1301 #[test]
1302 fn test_dijkstra_simple_path() {
1303 let graph = create_test_graph();
1304 let nodes = graph.sorted_node_ids();
1305 let start = nodes[0];
1306 let end = nodes[2];
1307
1308 let path = dijkstra_search(&graph, start, end, &ShortestPathOptions::default());
1309 assert!(path.is_ok());
1310
1311 let shortest = path.expect("Failed to find path");
1312 assert!(shortest.found);
1313 assert_eq!(shortest.cost, 2.0); }
1315
1316 #[test]
1317 fn test_dijkstra_no_path() {
1318 let mut graph = Graph::new();
1319 let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1320 let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1321 let path = dijkstra_search(&graph, n1, n2, &ShortestPathOptions::default());
1324 assert!(path.is_ok());
1325
1326 let shortest = path.expect("Failed to search");
1327 assert!(!shortest.found);
1328 }
1329
1330 #[test]
1331 fn test_astar_search() {
1332 let graph = create_test_graph();
1333 let nodes = graph.sorted_node_ids();
1334 let start = nodes[0];
1335 let end = nodes[4];
1336
1337 let path = astar_search(&graph, start, end, &ShortestPathOptions::default());
1338 assert!(path.is_ok());
1339
1340 let shortest = path.expect("Failed to find path");
1341 assert!(shortest.found);
1342 }
1343
1344 #[test]
1345 fn test_bidirectional_search() {
1346 let graph = create_test_graph();
1347 let nodes = graph.sorted_node_ids();
1348 let start = nodes[0];
1349 let end = nodes[4];
1350
1351 let path = bidirectional_search(&graph, start, end, &ShortestPathOptions::default());
1352 assert!(path.is_ok());
1353
1354 let shortest = path.expect("Failed to find path");
1355 assert!(shortest.found);
1356 }
1357
1358 #[test]
1359 fn test_max_length_constraint() {
1360 let graph = create_test_graph();
1361 let nodes = graph.sorted_node_ids();
1362 let start = nodes[0];
1363 let end = nodes[4];
1364
1365 let options = ShortestPathOptions {
1366 max_length: Some(2.0), ..Default::default()
1368 };
1369
1370 let path = dijkstra_search(&graph, start, end, &options);
1371 assert!(path.is_ok());
1372
1373 let shortest = path.expect("Failed to search");
1374 assert!(!shortest.found);
1375 }
1376
1377 #[test]
1378 fn test_turn_restricted_dijkstra() {
1379 let mut graph = Graph::new();
1380
1381 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1389 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1390 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1391 let n3 = graph.add_node(Coordinate::new_2d(1.0, 1.0));
1392
1393 let e0 = graph.add_edge(n0, n1, 1.0).expect("edge");
1394 let e1 = graph.add_edge(n1, n2, 1.0).expect("edge");
1395 let e2 = graph.add_edge(n1, n3, 1.0).expect("edge");
1396 let _ = graph.add_edge(n3, n2, 2.0).expect("edge");
1397
1398 let path = dijkstra_search(&graph, n0, n2, &ShortestPathOptions::default());
1400 assert!(path.is_ok());
1401 let shortest = path.expect("path");
1402 assert_eq!(shortest.cost, 2.0);
1403
1404 let mut tp = TurnPenalties::new();
1406 tp.add_prohibition(n1, e0, e1);
1407
1408 let options = ShortestPathOptions {
1409 turn_penalties: Some(tp),
1410 ..Default::default()
1411 };
1412
1413 let path = dijkstra_turn_restricted(&graph, n0, n2, &options);
1415 assert!(path.is_ok());
1416 let shortest = path.expect("path");
1417 assert!(shortest.found);
1418 assert_eq!(shortest.cost, 4.0);
1419 }
1420
1421 #[test]
1422 fn test_turn_penalty() {
1423 let mut graph = Graph::new();
1424
1425 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1426 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1427 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1428
1429 let e0 = graph.add_edge(n0, n1, 1.0).expect("edge");
1430 let e1 = graph.add_edge(n1, n2, 1.0).expect("edge");
1431
1432 let mut tp = TurnPenalties::new();
1433 tp.add_penalty(n1, e0, e1, 5.0);
1434
1435 let options = ShortestPathOptions {
1436 turn_penalties: Some(tp),
1437 ..Default::default()
1438 };
1439
1440 let path = dijkstra_turn_restricted(&graph, n0, n2, &options);
1441 assert!(path.is_ok());
1442 let shortest = path.expect("path");
1443 assert!(shortest.found);
1444 assert!((shortest.cost - 7.0).abs() < 1e-10);
1446 }
1447
1448 #[test]
1449 fn test_time_dependent_dijkstra() {
1450 let mut graph = Graph::new();
1451 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1452 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1453
1454 let e0 = graph.add_edge(n0, n1, 100.0).expect("edge"); let mut td_weights = HashMap::new();
1458 td_weights.insert(
1459 e0,
1460 TimeDependentWeight::new(vec![
1461 (0.0, 1.0),
1462 (25200.0, 2.0), (32400.0, 1.0), ]),
1465 );
1466
1467 let options = ShortestPathOptions {
1469 time_dependent_weights: Some(td_weights.clone()),
1470 departure_time: 28800.0,
1471 ..Default::default()
1472 };
1473
1474 let path = time_dependent_dijkstra(&graph, n0, n1, &options);
1475 assert!(path.is_ok());
1476 let shortest = path.expect("path");
1477 assert!(shortest.found);
1478 assert!((shortest.cost - 200.0).abs() < 1e-10); let options2 = ShortestPathOptions {
1482 time_dependent_weights: Some(td_weights),
1483 departure_time: 43200.0,
1484 ..Default::default()
1485 };
1486
1487 let path2 = time_dependent_dijkstra(&graph, n0, n1, &options2);
1488 assert!(path2.is_ok());
1489 let shortest2 = path2.expect("path");
1490 assert!((shortest2.cost - 100.0).abs() < 1e-10); }
1492
1493 #[test]
1494 fn test_floyd_warshall() {
1495 let mut graph = Graph::new();
1496 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1497 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1498 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1499
1500 let _ = graph.add_edge(n0, n1, 1.0);
1501 let _ = graph.add_edge(n1, n2, 2.0);
1502 let _ = graph.add_edge(n0, n2, 5.0);
1503
1504 let result = floyd_warshall(&graph, 100);
1505 assert!(result.is_ok());
1506
1507 let apsp = result.expect("Floyd-Warshall failed");
1508 assert!((apsp.distance(n0, n2) - 3.0).abs() < 1e-10); assert!((apsp.distance(n0, n1) - 1.0).abs() < 1e-10);
1510 }
1511
1512 #[test]
1513 fn test_floyd_warshall_path_reconstruction() {
1514 let mut graph = Graph::new();
1515 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1516 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1517 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1518
1519 let _ = graph.add_edge(n0, n1, 1.0);
1520 let _ = graph.add_edge(n1, n2, 2.0);
1521 let _ = graph.add_edge(n0, n2, 5.0);
1522
1523 let apsp = floyd_warshall(&graph, 100).expect("Floyd-Warshall failed");
1524
1525 let path = apsp.path(n0, n2);
1526 assert!(path.is_some());
1527 let path = path.expect("path");
1528 assert_eq!(path, vec![n0, n1, n2]);
1529 }
1530
1531 #[test]
1532 fn test_floyd_warshall_too_large() {
1533 let mut graph = Graph::new();
1534 for i in 0..20 {
1535 graph.add_node(Coordinate::new_2d(i as f64, 0.0));
1536 }
1537
1538 let result = floyd_warshall(&graph, 10);
1539 assert!(result.is_err());
1540 }
1541
1542 #[test]
1543 fn test_single_source_dijkstra() {
1544 let graph = create_test_graph();
1545 let nodes = graph.sorted_node_ids();
1546 let start = nodes[0];
1547
1548 let result = dijkstra_single_source(&graph, start, &ShortestPathOptions::default());
1549 assert!(result.is_ok());
1550
1551 let distances = result.expect("Failed single-source Dijkstra");
1552 assert!((distances.get(&start).expect("start").0 - 0.0).abs() < 1e-10);
1554 }
1555
1556 #[test]
1557 fn test_astar_turn_restricted() {
1558 let mut graph = Graph::new();
1559 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1560 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1561 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1562 let n3 = graph.add_node(Coordinate::new_2d(1.0, 1.0));
1563
1564 let e0 = graph.add_edge(n0, n1, 1.0).expect("edge");
1565 let e1 = graph.add_edge(n1, n2, 1.0).expect("edge");
1566 let _ = graph.add_edge(n1, n3, 1.0).expect("edge");
1567 let _ = graph.add_edge(n3, n2, 2.0).expect("edge");
1568
1569 let mut tp = TurnPenalties::new();
1571 tp.add_prohibition(n1, e0, e1);
1572
1573 let options = ShortestPathOptions {
1574 turn_penalties: Some(tp),
1575 ..Default::default()
1576 };
1577
1578 let path = astar_turn_restricted(&graph, n0, n2, &options);
1579 assert!(path.is_ok());
1580 let shortest = path.expect("path");
1581 assert!(shortest.found);
1582 assert!((shortest.cost - 4.0).abs() < 1e-10);
1584 }
1585
1586 #[test]
1587 fn test_nodes_visited_count() {
1588 let graph = create_test_graph();
1589 let nodes = graph.sorted_node_ids();
1590 let start = nodes[0];
1591 let end = nodes[4];
1592
1593 let path = dijkstra_search(&graph, start, end, &ShortestPathOptions::default());
1594 let shortest = path.expect("path");
1595 assert!(shortest.nodes_visited > 0);
1596 }
1597
1598 #[test]
1599 fn test_bidirectional_same_node() {
1600 let mut graph = Graph::new();
1601 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1602
1603 let path = bidirectional_search(&graph, n0, n0, &ShortestPathOptions::default());
1604 assert!(path.is_ok());
1605 let shortest = path.expect("path");
1606 assert!(shortest.found);
1607 assert_eq!(shortest.cost, 0.0);
1608 }
1609}