1use crate::error::{AlgorithmError, Result};
12use crate::vector::network::{
13 EdgeId, EdgeWeight, Graph, NodeId, ShortestPath, ShortestPathOptions, TurnPenalties,
14 astar_search, dijkstra_search, dijkstra_turn_restricted,
15};
16use oxigdal_core::vector::Coordinate;
17use std::collections::{HashMap, HashSet};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum RoutingAlgorithm {
22 Fastest,
24 Shortest,
26 Economical,
28 MultiCriteria,
30}
31
32#[derive(Debug, Clone)]
34pub struct TurnRestriction {
35 pub node: NodeId,
37 pub from_edge: EdgeId,
39 pub to_edge: EdgeId,
41 pub prohibited: bool,
43}
44
45#[derive(Debug, Clone)]
47pub struct RoutingCriteria {
48 pub distance_weight: f64,
50 pub time_weight: f64,
52 pub monetary_weight: f64,
54 pub custom_weights: HashMap<String, f64>,
56}
57
58impl Default for RoutingCriteria {
59 fn default() -> Self {
60 Self {
61 distance_weight: 1.0,
62 time_weight: 0.0,
63 monetary_weight: 0.0,
64 custom_weights: HashMap::new(),
65 }
66 }
67}
68
69impl RoutingCriteria {
70 pub fn shortest() -> Self {
72 Self {
73 distance_weight: 1.0,
74 time_weight: 0.0,
75 monetary_weight: 0.0,
76 custom_weights: HashMap::new(),
77 }
78 }
79
80 pub fn fastest() -> Self {
82 Self {
83 distance_weight: 0.0,
84 time_weight: 1.0,
85 monetary_weight: 0.0,
86 custom_weights: HashMap::new(),
87 }
88 }
89
90 pub fn cheapest() -> Self {
92 Self {
93 distance_weight: 0.0,
94 time_weight: 0.0,
95 monetary_weight: 1.0,
96 custom_weights: HashMap::new(),
97 }
98 }
99
100 pub fn balanced() -> Self {
102 Self {
103 distance_weight: 0.4,
104 time_weight: 0.4,
105 monetary_weight: 0.2,
106 custom_weights: HashMap::new(),
107 }
108 }
109
110 pub fn to_weight_criteria(&self) -> HashMap<String, f64> {
112 let mut map = HashMap::new();
113 if self.distance_weight > 0.0 {
114 map.insert("distance".to_string(), self.distance_weight);
115 }
116 if self.time_weight > 0.0 {
117 map.insert("time".to_string(), self.time_weight);
118 }
119 if self.monetary_weight > 0.0 {
120 map.insert("monetary".to_string(), self.monetary_weight);
121 }
122 for (k, v) in &self.custom_weights {
123 if *v > 0.0 {
124 map.insert(k.clone(), *v);
125 }
126 }
127 map
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct RouteOptions {
134 pub algorithm: RoutingAlgorithm,
136 pub turn_restrictions: Vec<TurnRestriction>,
138 pub avoid_tolls: bool,
140 pub avoid_highways: bool,
142 pub max_detour_factor: Option<f64>,
144 pub criteria: RoutingCriteria,
146 pub turn_penalties: Option<TurnPenalties>,
148 pub alternatives: usize,
150 pub alternative_overlap_threshold: f64,
152 pub via_points: Vec<NodeId>,
154}
155
156impl Default for RouteOptions {
157 fn default() -> Self {
158 Self {
159 algorithm: RoutingAlgorithm::Shortest,
160 turn_restrictions: Vec::new(),
161 avoid_tolls: false,
162 avoid_highways: false,
163 max_detour_factor: None,
164 criteria: RoutingCriteria::default(),
165 turn_penalties: None,
166 alternatives: 0,
167 alternative_overlap_threshold: 0.6,
168 via_points: Vec::new(),
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
175pub struct RouteSegment {
176 pub edge: EdgeId,
178 pub distance: f64,
180 pub travel_time: f64,
182 pub monetary_cost: f64,
184 pub instruction: Option<String>,
186 pub turn_angle: Option<f64>,
188}
189
190#[derive(Debug, Clone)]
192pub struct Route {
193 pub segments: Vec<RouteSegment>,
195 pub total_distance: f64,
197 pub total_time: f64,
199 pub total_monetary_cost: f64,
201 pub node_sequence: Vec<NodeId>,
203 pub coordinates: Vec<Coordinate>,
205 pub is_primary: bool,
207 pub quality_score: f64,
209}
210
211impl Route {
212 pub fn new() -> Self {
214 Self {
215 segments: Vec::new(),
216 total_distance: 0.0,
217 total_time: 0.0,
218 total_monetary_cost: 0.0,
219 node_sequence: Vec::new(),
220 coordinates: Vec::new(),
221 is_primary: true,
222 quality_score: 0.0,
223 }
224 }
225
226 fn edge_set(&self) -> HashSet<EdgeId> {
228 self.segments.iter().map(|s| s.edge).collect()
229 }
230
231 pub fn overlap_ratio(&self, other: &Route) -> f64 {
233 let self_edges = self.edge_set();
234 let other_edges = other.edge_set();
235
236 if self_edges.is_empty() || other_edges.is_empty() {
237 return 0.0;
238 }
239
240 let intersection = self_edges.intersection(&other_edges).count();
241 let min_size = self_edges.len().min(other_edges.len());
242
243 if min_size == 0 {
244 return 0.0;
245 }
246
247 intersection as f64 / min_size as f64
248 }
249}
250
251impl Default for Route {
252 fn default() -> Self {
253 Self::new()
254 }
255}
256
257#[derive(Debug, Clone)]
259pub struct RouteResult {
260 pub primary: Route,
262 pub alternatives: Vec<Route>,
264}
265
266pub fn calculate_route(
268 graph: &Graph,
269 start: NodeId,
270 end: NodeId,
271 options: &RouteOptions,
272) -> Result<Route> {
273 let path_options = build_path_options(options);
275
276 if !options.via_points.is_empty() {
278 return calculate_via_route(graph, start, end, options);
279 }
280
281 let shortest = if options.turn_penalties.is_some() || !options.turn_restrictions.is_empty() {
283 let mut tp = options.turn_penalties.clone().unwrap_or_default();
284 for restriction in &options.turn_restrictions {
286 if restriction.prohibited {
287 tp.add_prohibition(restriction.node, restriction.from_edge, restriction.to_edge);
288 }
289 }
290 let mut opts = path_options.clone();
291 opts.turn_penalties = Some(tp);
292 dijkstra_turn_restricted(graph, start, end, &opts)?
293 } else {
294 match options.algorithm {
295 RoutingAlgorithm::Fastest | RoutingAlgorithm::MultiCriteria => {
296 astar_search(graph, start, end, &path_options)?
297 }
298 _ => dijkstra_search(graph, start, end, &path_options)?,
299 }
300 };
301
302 if !shortest.found {
303 return Err(AlgorithmError::PathNotFound(format!(
304 "No route found from {:?} to {:?}",
305 start, end
306 )));
307 }
308
309 build_route_from_path(graph, &shortest, true)
310}
311
312fn calculate_via_route(
314 graph: &Graph,
315 start: NodeId,
316 end: NodeId,
317 options: &RouteOptions,
318) -> Result<Route> {
319 let mut full_route = Route::new();
320 let path_options = build_path_options(options);
321
322 let mut waypoints = vec![start];
324 waypoints.extend_from_slice(&options.via_points);
325 waypoints.push(end);
326
327 for window in waypoints.windows(2) {
328 let from = window[0];
329 let to = window[1];
330
331 let shortest = dijkstra_search(graph, from, to, &path_options)?;
332
333 if !shortest.found {
334 return Err(AlgorithmError::PathNotFound(format!(
335 "No route found from {:?} to {:?} (via-point segment)",
336 from, to
337 )));
338 }
339
340 let segment_route = build_route_from_path(graph, &shortest, false)?;
341
342 if full_route.node_sequence.is_empty() {
344 full_route
345 .node_sequence
346 .extend(&segment_route.node_sequence);
347 } else {
348 full_route
350 .node_sequence
351 .extend(&segment_route.node_sequence[1..]);
352 }
353
354 full_route.segments.extend(segment_route.segments);
355 full_route.total_distance += segment_route.total_distance;
356 full_route.total_time += segment_route.total_time;
357 full_route.total_monetary_cost += segment_route.total_monetary_cost;
358 full_route.coordinates.extend(segment_route.coordinates);
359 }
360
361 full_route.is_primary = true;
362 full_route.quality_score = full_route.total_distance;
363
364 Ok(full_route)
365}
366
367fn build_path_options(options: &RouteOptions) -> ShortestPathOptions {
369 let weight_criteria = match options.algorithm {
370 RoutingAlgorithm::Fastest => Some(RoutingCriteria::fastest().to_weight_criteria()),
371 RoutingAlgorithm::Shortest => Some(RoutingCriteria::shortest().to_weight_criteria()),
372 RoutingAlgorithm::Economical => Some(RoutingCriteria::cheapest().to_weight_criteria()),
373 RoutingAlgorithm::MultiCriteria => Some(options.criteria.to_weight_criteria()),
374 };
375
376 ShortestPathOptions {
377 include_geometry: true,
378 weight_criteria,
379 turn_penalties: options.turn_penalties.clone(),
380 ..Default::default()
381 }
382}
383
384fn build_route_from_path(graph: &Graph, path: &ShortestPath, is_primary: bool) -> Result<Route> {
386 let mut route = Route::new();
387 let mut coordinates = Vec::new();
388
389 for (i, &edge_id) in path.edges.iter().enumerate() {
390 if let Some(edge) = graph.get_edge(edge_id) {
391 let distance = edge.multi_weight.distance;
392 let time_cost = edge.multi_weight.time;
393 let monetary = edge.multi_weight.monetary;
394
395 let travel_time = if time_cost > 0.0 {
396 time_cost
397 } else {
398 edge.travel_time()
399 .map(|t| t * 3600.0) .unwrap_or(distance / 13.89) };
402
403 let turn_angle = if i > 0 {
405 calculate_turn_angle(graph, path.edges[i - 1], edge_id)
406 } else {
407 None
408 };
409
410 let instruction = turn_angle.map(|angle| generate_turn_instruction(angle));
411
412 let segment = RouteSegment {
413 edge: edge_id,
414 distance,
415 travel_time,
416 monetary_cost: monetary,
417 instruction,
418 turn_angle,
419 };
420
421 route.segments.push(segment);
422 route.total_distance += distance;
423 route.total_time += travel_time;
424 route.total_monetary_cost += monetary;
425
426 if let Some(geom) = &edge.geometry {
428 coordinates.extend(geom.coords.iter().copied());
429 }
430 }
431 }
432
433 route.node_sequence = path.nodes.clone();
434 route.coordinates = coordinates;
435 route.is_primary = is_primary;
436 route.quality_score = path.cost;
437
438 Ok(route)
439}
440
441fn calculate_turn_angle(graph: &Graph, edge1_id: EdgeId, edge2_id: EdgeId) -> Option<f64> {
443 let edge1 = graph.get_edge(edge1_id)?;
444 let edge2 = graph.get_edge(edge2_id)?;
445
446 let src1 = graph.get_node(edge1.source)?;
447 let via = graph.get_node(edge1.target)?;
448 let dst2 = graph.get_node(edge2.target)?;
449
450 let v1x = via.coordinate.x - src1.coordinate.x;
452 let v1y = via.coordinate.y - src1.coordinate.y;
453
454 let v2x = dst2.coordinate.x - via.coordinate.x;
456 let v2y = dst2.coordinate.y - via.coordinate.y;
457
458 let cross = v1x * v2y - v1y * v2x;
460 let dot = v1x * v2x + v1y * v2y;
462
463 let angle = cross.atan2(dot).to_degrees();
465
466 Some(angle)
467}
468
469fn generate_turn_instruction(angle: f64) -> String {
471 if angle.abs() < 15.0 {
472 "Continue straight".to_string()
473 } else if (15.0..60.0).contains(&angle) {
474 "Bear left".to_string()
475 } else if (60.0..120.0).contains(&angle) {
476 "Turn left".to_string()
477 } else if angle >= 120.0 {
478 "Sharp left".to_string()
479 } else if angle <= -15.0 && angle > -60.0 {
480 "Bear right".to_string()
481 } else if angle <= -60.0 && angle > -120.0 {
482 "Turn right".to_string()
483 } else {
484 "Sharp right".to_string()
485 }
486}
487
488pub fn calculate_route_with_alternatives(
490 graph: &Graph,
491 start: NodeId,
492 end: NodeId,
493 options: &RouteOptions,
494) -> Result<RouteResult> {
495 let primary = calculate_route(graph, start, end, options)?;
497
498 let mut alternatives = Vec::new();
499 let num_alternatives = options.alternatives;
500
501 if num_alternatives == 0 {
502 return Ok(RouteResult {
503 primary,
504 alternatives,
505 });
506 }
507
508 let mut penalized_edges: HashMap<EdgeId, f64> = HashMap::new();
511
512 for segment in &primary.segments {
514 let current = penalized_edges.entry(segment.edge).or_insert(1.0);
515 *current *= 2.0; }
517
518 let path_options = build_path_options(options);
519
520 for alt_idx in 0..num_alternatives {
521 let mut alt_graph = graph.clone();
523
524 for (&edge_id, &penalty_multiplier) in &penalized_edges {
525 if let Some(edge) = alt_graph.get_edge_mut(edge_id) {
526 edge.weight *= penalty_multiplier;
527 edge.multi_weight.distance *= penalty_multiplier;
528 edge.multi_weight.time *= penalty_multiplier;
529 }
530 }
531
532 let shortest = dijkstra_search(&alt_graph, start, end, &path_options)?;
533
534 if !shortest.found {
535 continue;
536 }
537
538 let alt_route_result = build_alternative_route(graph, &shortest);
540 let alt_route = match alt_route_result {
541 Ok(r) => r,
542 Err(_) => continue,
543 };
544
545 let overlap_with_primary = alt_route.overlap_ratio(&primary);
547 let overlap_with_others = alternatives
548 .iter()
549 .map(|other: &Route| alt_route.overlap_ratio(other))
550 .fold(0.0f64, |a, b| a.max(b));
551
552 let max_overlap = overlap_with_primary.max(overlap_with_others);
553
554 if max_overlap < options.alternative_overlap_threshold {
555 for segment in &alt_route.segments {
557 let current = penalized_edges.entry(segment.edge).or_insert(1.0);
558 *current *= 1.5;
559 }
560
561 alternatives.push(alt_route);
562 } else {
563 for segment in &alt_route.segments {
565 let current = penalized_edges.entry(segment.edge).or_insert(1.0);
566 *current *= 3.0;
567 }
568 }
569
570 if alternatives.len() >= num_alternatives {
571 break;
572 }
573
574 if alt_idx > num_alternatives * 5 {
576 break;
577 }
578 }
579
580 alternatives.sort_by(|a, b| {
582 a.quality_score
583 .partial_cmp(&b.quality_score)
584 .unwrap_or(std::cmp::Ordering::Equal)
585 });
586
587 Ok(RouteResult {
588 primary,
589 alternatives,
590 })
591}
592
593fn build_alternative_route(graph: &Graph, path: &ShortestPath) -> Result<Route> {
595 let mut route = Route::new();
596 let mut coordinates = Vec::new();
597 let mut actual_cost = 0.0;
598
599 for (i, &edge_id) in path.edges.iter().enumerate() {
600 if let Some(edge) = graph.get_edge(edge_id) {
601 let distance = edge.multi_weight.distance;
602 let time_cost = edge.multi_weight.time;
603 let monetary = edge.multi_weight.monetary;
604
605 let travel_time = if time_cost > 0.0 {
606 time_cost
607 } else {
608 edge.travel_time()
609 .map(|t| t * 3600.0)
610 .unwrap_or(distance / 13.89)
611 };
612
613 actual_cost += edge.weight; let turn_angle = if i > 0 {
616 calculate_turn_angle(graph, path.edges[i - 1], edge_id)
617 } else {
618 None
619 };
620
621 let instruction = turn_angle.map(|angle| generate_turn_instruction(angle));
622
623 let segment = RouteSegment {
624 edge: edge_id,
625 distance,
626 travel_time,
627 monetary_cost: monetary,
628 instruction,
629 turn_angle,
630 };
631
632 route.segments.push(segment);
633 route.total_distance += distance;
634 route.total_time += travel_time;
635 route.total_monetary_cost += monetary;
636
637 if let Some(geom) = &edge.geometry {
638 coordinates.extend(geom.coords.iter().copied());
639 }
640 }
641 }
642
643 route.node_sequence = path.nodes.clone();
644 route.coordinates = coordinates;
645 route.is_primary = false;
646 route.quality_score = actual_cost;
647
648 Ok(route)
649}
650
651pub fn optimize_waypoint_order(
666 graph: &Graph,
667 waypoints: &[NodeId],
668 start: NodeId,
669 end: Option<NodeId>,
670 options: &RouteOptions,
671) -> Result<WaypointOptimizationResult> {
672 if waypoints.is_empty() {
673 return Ok(WaypointOptimizationResult {
674 optimized_order: Vec::new(),
675 total_cost: 0.0,
676 route: Route::new(),
677 });
678 }
679
680 if waypoints.len() == 1 {
681 let mut opts = options.clone();
682 opts.via_points = waypoints.to_vec();
683 let end_node = end.unwrap_or(start);
684 let route = calculate_route(graph, start, end_node, &opts)?;
685 return Ok(WaypointOptimizationResult {
686 optimized_order: waypoints.to_vec(),
687 total_cost: route.total_distance,
688 route,
689 });
690 }
691
692 let path_options = build_path_options(options);
693
694 let mut all_points = vec![start];
696 all_points.extend_from_slice(waypoints);
697 if let Some(e) = end {
698 if !all_points.contains(&e) {
699 all_points.push(e);
700 }
701 }
702
703 let mut cost_matrix: HashMap<(NodeId, NodeId), f64> = HashMap::new();
704
705 for &from in &all_points {
706 for &to in &all_points {
707 if from == to {
708 cost_matrix.insert((from, to), 0.0);
709 continue;
710 }
711
712 let result = dijkstra_search(graph, from, to, &path_options);
713 let cost = match result {
714 Ok(path) if path.found => path.cost,
715 _ => f64::MAX / 2.0, };
717 cost_matrix.insert((from, to), cost);
718 }
719 }
720
721 let mut order = Vec::new();
723 let mut remaining: HashSet<NodeId> = waypoints.iter().copied().collect();
724 let mut current = start;
725
726 while !remaining.is_empty() {
727 let nearest = remaining
728 .iter()
729 .min_by(|&&a, &&b| {
730 let cost_a = cost_matrix.get(&(current, a)).copied().unwrap_or(f64::MAX);
731 let cost_b = cost_matrix.get(&(current, b)).copied().unwrap_or(f64::MAX);
732 cost_a
733 .partial_cmp(&cost_b)
734 .unwrap_or(std::cmp::Ordering::Equal)
735 })
736 .copied();
737
738 if let Some(next) = nearest {
739 order.push(next);
740 remaining.remove(&next);
741 current = next;
742 } else {
743 break;
744 }
745 }
746
747 let improved_order = two_opt_improve(&order, start, end, &cost_matrix);
749
750 let total_cost = calculate_order_cost(&improved_order, start, end, &cost_matrix);
752
753 let mut opts = options.clone();
755 opts.via_points = improved_order.clone();
756 let end_node = end.unwrap_or(start);
757 let route = calculate_route(graph, start, end_node, &opts)?;
758
759 Ok(WaypointOptimizationResult {
760 optimized_order: improved_order,
761 total_cost,
762 route,
763 })
764}
765
766fn two_opt_improve(
768 order: &[NodeId],
769 start: NodeId,
770 end: Option<NodeId>,
771 cost_matrix: &HashMap<(NodeId, NodeId), f64>,
772) -> Vec<NodeId> {
773 let mut best_order = order.to_vec();
774 let mut best_cost = calculate_order_cost(&best_order, start, end, cost_matrix);
775 let mut improved = true;
776
777 while improved {
778 improved = false;
779
780 for i in 0..best_order.len().saturating_sub(1) {
781 for j in (i + 1)..best_order.len() {
782 let mut new_order = best_order.clone();
784 new_order[i..=j].reverse();
785
786 let new_cost = calculate_order_cost(&new_order, start, end, cost_matrix);
787
788 if new_cost < best_cost - 1e-10 {
789 best_order = new_order;
790 best_cost = new_cost;
791 improved = true;
792 }
793 }
794 }
795 }
796
797 best_order
798}
799
800fn calculate_order_cost(
802 order: &[NodeId],
803 start: NodeId,
804 end: Option<NodeId>,
805 cost_matrix: &HashMap<(NodeId, NodeId), f64>,
806) -> f64 {
807 let mut total = 0.0;
808 let mut current = start;
809
810 for &next in order {
811 total += cost_matrix
812 .get(&(current, next))
813 .copied()
814 .unwrap_or(f64::MAX / 2.0);
815 current = next;
816 }
817
818 if let Some(end_node) = end {
819 total += cost_matrix
820 .get(&(current, end_node))
821 .copied()
822 .unwrap_or(f64::MAX / 2.0);
823 }
824
825 total
826}
827
828#[derive(Debug, Clone)]
830pub struct WaypointOptimizationResult {
831 pub optimized_order: Vec<NodeId>,
833 pub total_cost: f64,
835 pub route: Route,
837}
838
839pub fn calculate_routes_batch(
841 graph: &Graph,
842 pairs: &[(NodeId, NodeId)],
843 options: &RouteOptions,
844) -> Result<Vec<Route>> {
845 pairs
846 .iter()
847 .map(|(start, end)| calculate_route(graph, *start, *end, options))
848 .collect()
849}
850
851pub fn od_matrix(
855 graph: &Graph,
856 origins: &[NodeId],
857 destinations: &[NodeId],
858 options: &RouteOptions,
859) -> Result<Vec<Vec<f64>>> {
860 let path_options = build_path_options(options);
861
862 let mut matrix = Vec::with_capacity(origins.len());
863
864 for &origin in origins {
865 let mut row = Vec::with_capacity(destinations.len());
866
867 let sssp = crate::vector::network::dijkstra_single_source(graph, origin, &path_options)?;
869
870 for &dest in destinations {
871 let cost = sssp.get(&dest).map(|(c, _)| *c).unwrap_or(f64::INFINITY);
872 row.push(cost);
873 }
874
875 matrix.push(row);
876 }
877
878 Ok(matrix)
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884
885 fn create_routing_graph() -> Graph {
886 let mut graph = Graph::new();
887
888 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
895 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
896 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
897 let n3 = graph.add_node(Coordinate::new_2d(0.0, 1.0));
898 let n4 = graph.add_node(Coordinate::new_2d(1.0, 1.0));
899 let n5 = graph.add_node(Coordinate::new_2d(2.0, 1.0));
900
901 let _ = graph.add_edge(n0, n1, 1.0);
902 let _ = graph.add_edge(n1, n2, 1.0);
903 let _ = graph.add_edge(n0, n3, 2.0);
904 let _ = graph.add_edge(n3, n4, 3.0);
905 let _ = graph.add_edge(n4, n2, 1.0);
906 let _ = graph.add_edge(n4, n5, 1.0);
907 let _ = graph.add_edge(n2, n5, 1.0);
908
909 graph
910 }
911
912 #[test]
913 fn test_route_creation() {
914 let route = Route::new();
915 assert_eq!(route.segments.len(), 0);
916 assert_eq!(route.total_distance, 0.0);
917 }
918
919 #[test]
920 fn test_route_options() {
921 let options = RouteOptions::default();
922 assert_eq!(options.algorithm, RoutingAlgorithm::Shortest);
923 assert!(!options.avoid_tolls);
924 }
925
926 #[test]
927 fn test_calculate_route() {
928 let graph = create_routing_graph();
929 let nodes = graph.sorted_node_ids();
930
931 let route = calculate_route(&graph, nodes[0], nodes[5], &RouteOptions::default());
932 assert!(route.is_ok());
933
934 let r = route.expect("route");
935 assert!(!r.segments.is_empty());
936 assert!(r.total_distance > 0.0);
937 }
938
939 #[test]
940 fn test_routing_criteria() {
941 let fastest = RoutingCriteria::fastest();
942 let criteria_map = fastest.to_weight_criteria();
943 assert!(criteria_map.contains_key("time"));
944 assert!(!criteria_map.contains_key("distance"));
945 }
946
947 #[test]
948 fn test_via_point_routing() {
949 let graph = create_routing_graph();
950 let nodes = graph.sorted_node_ids();
951
952 let mut options = RouteOptions::default();
953 options.via_points = vec![nodes[1]]; let route = calculate_route(&graph, nodes[0], nodes[5], &options);
956 assert!(route.is_ok());
957
958 let r = route.expect("route");
959 assert!(r.node_sequence.contains(&nodes[1]));
960 }
961
962 #[test]
963 fn test_route_overlap() {
964 let mut route1 = Route::new();
965 route1.segments.push(RouteSegment {
966 edge: EdgeId(0),
967 distance: 1.0,
968 travel_time: 1.0,
969 monetary_cost: 0.0,
970 instruction: None,
971 turn_angle: None,
972 });
973 route1.segments.push(RouteSegment {
974 edge: EdgeId(1),
975 distance: 1.0,
976 travel_time: 1.0,
977 monetary_cost: 0.0,
978 instruction: None,
979 turn_angle: None,
980 });
981
982 let mut route2 = Route::new();
983 route2.segments.push(RouteSegment {
984 edge: EdgeId(0),
985 distance: 1.0,
986 travel_time: 1.0,
987 monetary_cost: 0.0,
988 instruction: None,
989 turn_angle: None,
990 });
991 route2.segments.push(RouteSegment {
992 edge: EdgeId(2),
993 distance: 1.0,
994 travel_time: 1.0,
995 monetary_cost: 0.0,
996 instruction: None,
997 turn_angle: None,
998 });
999
1000 let overlap = route1.overlap_ratio(&route2);
1001 assert!((overlap - 0.5).abs() < 1e-10);
1002 }
1003
1004 #[test]
1005 fn test_turn_instruction() {
1006 assert_eq!(generate_turn_instruction(0.0), "Continue straight");
1007 assert_eq!(generate_turn_instruction(90.0), "Turn left");
1008 assert_eq!(generate_turn_instruction(-90.0), "Turn right");
1009 assert_eq!(generate_turn_instruction(30.0), "Bear left");
1010 assert_eq!(generate_turn_instruction(-30.0), "Bear right");
1011 assert_eq!(generate_turn_instruction(150.0), "Sharp left");
1012 assert_eq!(generate_turn_instruction(-150.0), "Sharp right");
1013 }
1014
1015 #[test]
1016 fn test_batch_routes() {
1017 let graph = create_routing_graph();
1018 let nodes = graph.sorted_node_ids();
1019
1020 let pairs = vec![(nodes[0], nodes[5]), (nodes[0], nodes[2])];
1021 let result = calculate_routes_batch(&graph, &pairs, &RouteOptions::default());
1022 assert!(result.is_ok());
1023
1024 let routes = result.expect("batch routes");
1025 assert_eq!(routes.len(), 2);
1026 }
1027
1028 #[test]
1029 fn test_od_matrix() {
1030 let graph = create_routing_graph();
1031 let nodes = graph.sorted_node_ids();
1032
1033 let origins = vec![nodes[0], nodes[3]];
1034 let destinations = vec![nodes[2], nodes[5]];
1035
1036 let result = od_matrix(&graph, &origins, &destinations, &RouteOptions::default());
1037 assert!(result.is_ok());
1038
1039 let matrix = result.expect("od matrix");
1040 assert_eq!(matrix.len(), 2);
1041 assert_eq!(matrix[0].len(), 2);
1042 assert!((matrix[0][0] - 2.0).abs() < 1e-10);
1044 }
1045
1046 #[test]
1047 fn test_waypoint_optimization() {
1048 let mut graph = Graph::new();
1049
1050 let n0 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1052 let n1 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1053 let n2 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1054 let n3 = graph.add_node(Coordinate::new_2d(3.0, 0.0));
1055 let n4 = graph.add_node(Coordinate::new_2d(4.0, 0.0));
1056
1057 let _ = graph.add_edge(n0, n1, 1.0);
1058 let _ = graph.add_edge(n1, n2, 1.0);
1059 let _ = graph.add_edge(n2, n3, 1.0);
1060 let _ = graph.add_edge(n3, n4, 1.0);
1061 let _ = graph.add_edge(n1, n0, 1.0);
1063 let _ = graph.add_edge(n2, n1, 1.0);
1064 let _ = graph.add_edge(n3, n2, 1.0);
1065 let _ = graph.add_edge(n4, n3, 1.0);
1066
1067 let result =
1071 optimize_waypoint_order(&graph, &[n3, n1], n0, Some(n4), &RouteOptions::default());
1072 assert!(result.is_ok());
1073
1074 let opt = result.expect("optimization");
1075 assert_eq!(opt.optimized_order, vec![n1, n3]);
1077 }
1078}