1use serde::{Deserialize, Serialize};
4use std::collections::{HashMap, HashSet};
5
6use super::algo::{astar, kosaraju_scc};
7use super::coord::Coord;
8use super::error::RoutingError;
9use super::geo::{coord_key, haversine_distance};
10use super::graph::{DiGraph, EdgeIdx, NodeIdx};
11use super::spatial::{KdTree, Point2D, Segment, SegmentIndex};
12
13#[derive(Debug, Clone)]
14pub struct NodeData {
15 pub lat: f64,
16 pub lng: f64,
17}
18
19impl NodeData {
20 #[inline]
21 pub fn coord(&self) -> Coord {
22 Coord::new(self.lat, self.lng)
23 }
24}
25
26#[derive(Debug, Clone)]
27pub struct EdgeData {
28 pub travel_time_s: f64,
29 pub distance_m: f64,
30}
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct RouteResult {
34 pub duration_seconds: i64,
35 pub distance_meters: f64,
36 pub geometry: Vec<Coord>,
37}
38
39impl RouteResult {
40 pub fn simplify(mut self, tolerance_m: f64) -> Self {
46 if self.geometry.len() <= 2 {
47 return self;
48 }
49
50 self.geometry = douglas_peucker(&self.geometry, tolerance_m);
51 self
52 }
53}
54
55#[derive(Debug, Clone, Copy, Default)]
56pub struct SnappedCoord {
57 pub original: Coord,
58 pub snapped: Coord,
59 pub snap_distance_m: f64,
60 pub(crate) node_index: NodeIdx,
61}
62
63#[derive(Debug, Clone, Copy)]
65struct SpatialPoint {
66 node_index: NodeIdx,
67}
68
69#[derive(Debug, Clone, Copy)]
71struct SpatialSegment {
72 from_node: NodeIdx,
73 to_node: NodeIdx,
74 edge_index: EdgeIdx,
75}
76
77#[derive(Debug, Clone, Copy)]
78pub struct EdgeSnappedLocation {
79 pub original: Coord,
80 pub snapped: Coord,
81 pub snap_distance_m: f64,
82 pub(crate) edge_index: EdgeIdx,
83 pub(crate) position: f64,
84 pub(crate) from_node: NodeIdx,
85 pub(crate) to_node: NodeIdx,
86}
87
88#[derive(Debug, Clone, Copy)]
89struct EdgeTraversal {
90 node: NodeIdx,
91 time_s: f64,
92 distance_m: f64,
93}
94
95pub struct RoadNetwork {
96 pub(super) graph: DiGraph<NodeData, EdgeData>,
97 pub(super) coord_to_node: HashMap<(i64, i64), NodeIdx>,
98 spatial_index: Option<KdTree<SpatialPoint>>,
99 edge_spatial_index: Option<SegmentIndex<SpatialSegment>>,
100 max_speed_mps: f64,
101}
102
103impl RoadNetwork {
104 pub fn new() -> Self {
105 Self {
106 graph: DiGraph::new(),
107 coord_to_node: HashMap::new(),
108 spatial_index: None,
109 edge_spatial_index: None,
110 max_speed_mps: 0.0,
111 }
112 }
113
114 pub(super) fn get_or_create_node(&mut self, lat: f64, lng: f64) -> NodeIdx {
115 let key = coord_key(lat, lng);
116 if let Some(&idx) = self.coord_to_node.get(&key) {
117 idx
118 } else {
119 let idx = self.graph.add_node(NodeData { lat, lng });
120 self.coord_to_node.insert(key, idx);
121 idx
122 }
123 }
124
125 pub(super) fn add_node_at(&mut self, lat: f64, lng: f64) -> NodeIdx {
126 let idx = self.graph.add_node(NodeData { lat, lng });
127 let key = coord_key(lat, lng);
128 self.coord_to_node.insert(key, idx);
129 idx
130 }
131
132 pub(super) fn add_edge(&mut self, from: NodeIdx, to: NodeIdx, data: EdgeData) {
133 self.record_edge_speed(&data);
134 self.graph.add_edge(from, to, data);
135 }
136
137 pub(super) fn add_edge_by_index(
138 &mut self,
139 from: usize,
140 to: usize,
141 travel_time_s: f64,
142 distance_m: f64,
143 ) {
144 let from_idx = NodeIdx::new(from);
145 let to_idx = NodeIdx::new(to);
146 let data = EdgeData {
147 travel_time_s,
148 distance_m,
149 };
150 self.record_edge_speed(&data);
151 self.graph.add_edge(from_idx, to_idx, data);
152 }
153
154 pub(super) fn build_spatial_index(&mut self) {
155 let points: Vec<(Point2D, SpatialPoint)> = self
157 .graph
158 .node_indices()
159 .filter_map(|node_idx| {
160 let node = self.graph.node_weight(node_idx)?;
161 Some((
162 Point2D::new(node.lat, node.lng),
163 SpatialPoint {
164 node_index: node_idx,
165 },
166 ))
167 })
168 .collect();
169
170 self.spatial_index = Some(KdTree::from_items(points));
171
172 let segments: Vec<(Segment, SpatialSegment)> = self
174 .graph
175 .edge_indices()
176 .filter_map(|edge_idx| {
177 let (from_node, to_node) = self.graph.edge_endpoints(edge_idx)?;
178 let from_data = self.graph.node_weight(from_node)?;
179 let to_data = self.graph.node_weight(to_node)?;
180 Some((
181 Segment::new(
182 Point2D::new(from_data.lat, from_data.lng),
183 Point2D::new(to_data.lat, to_data.lng),
184 ),
185 SpatialSegment {
186 from_node,
187 to_node,
188 edge_index: edge_idx,
189 },
190 ))
191 })
192 .collect();
193
194 self.edge_spatial_index = Some(SegmentIndex::bulk_load(segments));
195 }
196
197 fn record_edge_speed(&mut self, edge: &EdgeData) {
198 if edge.travel_time_s <= 0.0 || edge.distance_m <= 0.0 {
199 return;
200 }
201
202 let speed_mps = edge.distance_m / edge.travel_time_s;
203 if speed_mps.is_finite() {
204 self.max_speed_mps = self.max_speed_mps.max(speed_mps);
205 }
206 }
207
208 fn distance_lower_bound_between(&self, from: NodeIdx, to: NodeIdx) -> f64 {
209 let Some(from_node) = self.graph.node_weight(from) else {
210 return 0.0;
211 };
212 let Some(to_node) = self.graph.node_weight(to) else {
213 return 0.0;
214 };
215 haversine_distance(from_node.coord(), to_node.coord())
216 }
217
218 fn time_lower_bound_between(&self, from: NodeIdx, to: NodeIdx) -> f64 {
219 if !self.max_speed_mps.is_finite() || self.max_speed_mps <= 0.0 {
220 return 0.0;
221 }
222
223 self.distance_lower_bound_between(from, to) / self.max_speed_mps
224 }
225
226 fn node_path_geometry(&self, path: &[NodeIdx]) -> Vec<Coord> {
227 path.iter()
228 .filter_map(|&idx| self.graph.node_weight(idx).map(|n| n.coord()))
229 .collect()
230 }
231
232 fn node_path_metrics(&self, path: &[NodeIdx]) -> (f64, f64) {
233 let mut distance = 0.0;
234 let mut time = 0.0;
235
236 for window in path.windows(2) {
237 if let Some(edge) = self.graph.find_edge(window[0], window[1]) {
238 if let Some(weight) = self.graph.edge_weight(edge) {
239 distance += weight.distance_m;
240 time += weight.travel_time_s;
241 }
242 }
243 }
244
245 (distance, time)
246 }
247
248 fn route_result_from_node_path(&self, path: &[NodeIdx], duration_seconds: i64) -> RouteResult {
249 let geometry = self.node_path_geometry(path);
250 let (distance, _) = self.node_path_metrics(path);
251
252 RouteResult {
253 duration_seconds,
254 distance_meters: distance,
255 geometry,
256 }
257 }
258
259 pub fn nodes_iter(&self) -> impl Iterator<Item = (f64, f64)> + '_ {
261 self.graph
262 .node_indices()
263 .filter_map(|idx| self.graph.node_weight(idx).map(|n| (n.lat, n.lng)))
264 }
265
266 pub fn edges_iter(&self) -> impl Iterator<Item = (usize, usize, f64, f64)> + '_ {
268 self.graph.edge_indices().filter_map(|idx| {
269 let (from, to) = self.graph.edge_endpoints(idx)?;
270 let weight = self.graph.edge_weight(idx)?;
271 Some((
272 from.index(),
273 to.index(),
274 weight.travel_time_s,
275 weight.distance_m,
276 ))
277 })
278 }
279
280 pub fn snap_to_road(&self, coord: Coord) -> Option<NodeIdx> {
284 self.spatial_index
285 .as_ref()?
286 .nearest_neighbor(&Point2D::new(coord.lat, coord.lng))
287 .map(|p| p.node_index)
288 }
289
290 pub fn snap_to_road_detailed(&self, coord: Coord) -> Result<SnappedCoord, RoutingError> {
295 let tree = self
296 .spatial_index
297 .as_ref()
298 .ok_or(RoutingError::SnapFailed {
299 coord,
300 nearest_distance_m: None,
301 })?;
302
303 let query = Point2D::new(coord.lat, coord.lng);
304 let (nearest, _dist_sq) =
305 tree.nearest_neighbor_with_distance(&query)
306 .ok_or(RoutingError::SnapFailed {
307 coord,
308 nearest_distance_m: None,
309 })?;
310
311 let node_data =
313 self.graph
314 .node_weight(nearest.node_index)
315 .ok_or(RoutingError::SnapFailed {
316 coord,
317 nearest_distance_m: None,
318 })?;
319
320 let snapped = Coord::new(node_data.lat, node_data.lng);
321 let snap_distance_m = haversine_distance(coord, snapped);
322
323 Ok(SnappedCoord {
324 original: coord,
325 snapped,
326 snap_distance_m,
327 node_index: nearest.node_index,
328 })
329 }
330
331 pub fn snap_to_edge(&self, coord: Coord) -> Result<EdgeSnappedLocation, RoutingError> {
336 let index = self
337 .edge_spatial_index
338 .as_ref()
339 .ok_or(RoutingError::SnapFailed {
340 coord,
341 nearest_distance_m: None,
342 })?;
343
344 let query = Point2D::new(coord.lat, coord.lng);
345 let (segment, seg_data, proj_point, _dist_sq) =
346 index
347 .nearest_segment(&query)
348 .ok_or(RoutingError::SnapFailed {
349 coord,
350 nearest_distance_m: None,
351 })?;
352
353 let (_, position) = segment.project_point(&query);
355 let snapped = Coord::new(proj_point.x, proj_point.y);
356 let snap_distance_m = haversine_distance(coord, snapped);
357
358 Ok(EdgeSnappedLocation {
359 original: coord,
360 snapped,
361 snap_distance_m,
362 edge_index: seg_data.edge_index,
363 position,
364 from_node: seg_data.from_node,
365 to_node: seg_data.to_node,
366 })
367 }
368
369 pub fn route(&self, from: Coord, to: Coord) -> Result<RouteResult, RoutingError> {
376 let start_snap = self.snap_to_road_detailed(from)?;
377 let end_snap = self.snap_to_road_detailed(to)?;
378
379 self.route_snapped(&start_snap, &end_snap)
380 }
381
382 pub fn route_edge_snapped(
387 &self,
388 from: &EdgeSnappedLocation,
389 to: &EdgeSnappedLocation,
390 ) -> Result<RouteResult, RoutingError> {
391 let from_edge = self
392 .graph
393 .edge_weight(from.edge_index)
394 .ok_or(RoutingError::NoPath {
395 from: from.original,
396 to: to.original,
397 })?;
398 let to_edge = self
399 .graph
400 .edge_weight(to.edge_index)
401 .ok_or(RoutingError::NoPath {
402 from: from.original,
403 to: to.original,
404 })?;
405
406 if from.edge_index == to.edge_index {
408 if to.position < from.position {
409 return Err(RoutingError::NoPath {
410 from: from.original,
411 to: to.original,
412 });
413 }
414
415 let segment_time = from_edge.travel_time_s;
416 let segment_dist = from_edge.distance_m;
417 let travel_fraction = to.position - from.position;
418 return Ok(RouteResult {
419 duration_seconds: (segment_time * travel_fraction).round() as i64,
420 distance_meters: segment_dist * travel_fraction,
421 geometry: vec![from.snapped, to.snapped],
422 });
423 }
424
425 let start_exit = EdgeTraversal {
426 node: from.to_node,
427 time_s: from_edge.travel_time_s * (1.0 - from.position),
428 distance_m: from_edge.distance_m * (1.0 - from.position),
429 };
430 let end_entry = EdgeTraversal {
431 node: to.from_node,
432 time_s: to_edge.travel_time_s * to.position,
433 distance_m: to_edge.distance_m * to.position,
434 };
435
436 let best_result = if start_exit.node == end_entry.node {
437 Some((start_exit.time_s + end_entry.time_s, vec![start_exit.node]))
438 } else {
439 astar(
440 &self.graph,
441 start_exit.node,
442 |n| n == end_entry.node,
443 |e| e.travel_time_s,
444 |n| self.time_lower_bound_between(n, end_entry.node),
445 )
446 .map(|(path_cost, path)| (start_exit.time_s + path_cost + end_entry.time_s, path))
447 };
448
449 match best_result {
450 Some((total_cost, path)) => {
451 let mut geometry = vec![from.snapped];
452 for coord in self.node_path_geometry(&path) {
453 if geometry.last().copied() != Some(coord) {
454 geometry.push(coord);
455 }
456 }
457 if geometry.last().copied() != Some(to.snapped) {
458 geometry.push(to.snapped);
459 }
460
461 let (path_distance, _) = self.node_path_metrics(&path);
462 let distance = start_exit.distance_m + path_distance + end_entry.distance_m;
463
464 Ok(RouteResult {
465 duration_seconds: total_cost.round() as i64,
466 distance_meters: distance,
467 geometry,
468 })
469 }
470 None => Err(RoutingError::NoPath {
471 from: from.original,
472 to: to.original,
473 }),
474 }
475 }
476
477 pub fn route_snapped(
482 &self,
483 from: &SnappedCoord,
484 to: &SnappedCoord,
485 ) -> Result<RouteResult, RoutingError> {
486 if from.node_index == to.node_index {
487 return Ok(RouteResult {
488 duration_seconds: 0,
489 distance_meters: 0.0,
490 geometry: vec![from.original, to.original],
491 });
492 }
493
494 let result = astar(
495 &self.graph,
496 from.node_index,
497 |n| n == to.node_index,
498 |e| e.travel_time_s,
499 |n| self.time_lower_bound_between(n, to.node_index),
500 );
501
502 match result {
503 Some((cost, path)) => Ok(self.route_result_from_node_path(&path, cost.round() as i64)),
504 None => Err(RoutingError::NoPath {
505 from: from.original,
506 to: to.original,
507 }),
508 }
509 }
510
511 pub fn route_with(
516 &self,
517 from: Coord,
518 to: Coord,
519 objective: Objective,
520 ) -> Result<RouteResult, RoutingError> {
521 let start_snap = self.snap_to_road_detailed(from)?;
522 let end_snap = self.snap_to_road_detailed(to)?;
523
524 if start_snap.node_index == end_snap.node_index {
525 return Ok(RouteResult {
526 duration_seconds: 0,
527 distance_meters: 0.0,
528 geometry: vec![from, to],
529 });
530 }
531
532 let result = match objective {
533 Objective::Time => astar(
534 &self.graph,
535 start_snap.node_index,
536 |n| n == end_snap.node_index,
537 |e| e.travel_time_s,
538 |n| self.time_lower_bound_between(n, end_snap.node_index),
539 ),
540 Objective::Distance => astar(
541 &self.graph,
542 start_snap.node_index,
543 |n| n == end_snap.node_index,
544 |e| e.distance_m,
545 |n| self.distance_lower_bound_between(n, end_snap.node_index),
546 ),
547 };
548
549 match result {
550 Some((_, path)) => {
551 let (distance, time) = self.node_path_metrics(&path);
552 Ok(RouteResult {
553 duration_seconds: time.round() as i64,
554 distance_meters: distance,
555 geometry: self.node_path_geometry(&path),
556 })
557 }
558 None => Err(RoutingError::NoPath { from, to }),
559 }
560 }
561
562 pub fn node_count(&self) -> usize {
563 self.graph.node_count()
564 }
565
566 pub fn edge_count(&self) -> usize {
567 self.graph.edge_count()
568 }
569
570 pub fn strongly_connected_components(&self) -> usize {
571 kosaraju_scc(&self.graph).len()
572 }
573
574 pub fn largest_component_fraction(&self) -> f64 {
575 let sccs = kosaraju_scc(&self.graph);
576 if sccs.is_empty() {
577 return 0.0;
578 }
579 let largest = sccs.iter().map(|c| c.len()).max().unwrap_or(0);
580 let total = self.graph.node_count();
581 if total == 0 {
582 0.0
583 } else {
584 largest as f64 / total as f64
585 }
586 }
587
588 pub fn is_strongly_connected(&self) -> bool {
589 self.strongly_connected_components() == 1
590 }
591
592 pub fn filter_to_largest_scc(&mut self) {
594 let sccs = kosaraju_scc(&self.graph);
595 if sccs.len() <= 1 {
596 return; }
598
599 let largest_scc: HashSet<NodeIdx> = sccs
601 .into_iter()
602 .max_by_key(|scc| scc.len())
603 .unwrap_or_default()
604 .into_iter()
605 .collect();
606
607 self.graph.retain_nodes(|n, _| largest_scc.contains(&n));
609
610 self.rebuild_coord_to_node();
611 self.build_spatial_index();
612 }
613
614 fn rebuild_coord_to_node(&mut self) {
615 self.coord_to_node.clear();
616 for idx in self.graph.node_indices() {
617 if let Some(node) = self.graph.node_weight(idx) {
618 let key = coord_key(node.lat, node.lng);
619 self.coord_to_node.insert(key, idx);
620 }
621 }
622 }
623}
624
625impl Default for RoadNetwork {
626 fn default() -> Self {
627 Self::new()
628 }
629}
630
631impl RoadNetwork {
632 pub fn from_test_data(nodes: &[(f64, f64)], edges: &[(usize, usize, f64, f64)]) -> Self {
650 let mut network = Self::new();
651
652 for &(lat, lng) in nodes {
654 network.add_node_at(lat, lng);
655 }
656
657 for &(from, to, time_s, dist_m) in edges {
659 network.add_edge_by_index(from, to, time_s, dist_m);
660 }
661
662 network.build_spatial_index();
664
665 network
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::{NodeIdx, Objective, RoadNetwork};
672 use crate::routing::Coord;
673
674 #[test]
675 fn time_routing_uses_non_zero_admissible_heuristic() {
676 let nodes = &[(0.0, 0.0), (0.0, 0.01), (0.01, 0.0), (0.01, 0.01)];
677 let edges = &[
678 (0, 1, 200.0, 1_200.0),
679 (1, 3, 200.0, 1_200.0),
680 (0, 2, 50.0, 1_200.0),
681 (2, 3, 50.0, 1_200.0),
682 (1, 0, 200.0, 1_200.0),
683 (3, 1, 200.0, 1_200.0),
684 (2, 0, 50.0, 1_200.0),
685 (3, 2, 50.0, 1_200.0),
686 ];
687 let network = RoadNetwork::from_test_data(nodes, edges);
688
689 let result = network
690 .route(Coord::new(0.0, 0.0), Coord::new(0.01, 0.01))
691 .expect("time route should exist");
692
693 assert_eq!(result.duration_seconds, 100);
694 assert_eq!(result.distance_meters, 2_400.0);
695 assert_eq!(
696 result.geometry,
697 vec![
698 Coord::new(0.0, 0.0),
699 Coord::new(0.01, 0.0),
700 Coord::new(0.01, 0.01),
701 ]
702 );
703 assert!(network.time_lower_bound_between(NodeIdx(0), NodeIdx(3)) > 0.0);
704 }
705
706 #[test]
707 fn distance_routing_uses_non_zero_admissible_heuristic() {
708 let nodes = &[(0.0, 0.0), (0.0, 0.02), (0.01, 0.0), (0.01, 0.02)];
709 let edges = &[
710 (0, 1, 40.0, 3_000.0),
711 (1, 3, 40.0, 3_000.0),
712 (0, 2, 90.0, 900.0),
713 (2, 3, 90.0, 900.0),
714 (1, 0, 40.0, 3_000.0),
715 (3, 1, 40.0, 3_000.0),
716 (2, 0, 90.0, 900.0),
717 (3, 2, 90.0, 900.0),
718 ];
719 let network = RoadNetwork::from_test_data(nodes, edges);
720
721 let result = network
722 .route_with(
723 Coord::new(0.0, 0.0),
724 Coord::new(0.01, 0.02),
725 Objective::Distance,
726 )
727 .expect("distance route should exist");
728
729 assert_eq!(result.distance_meters, 1_800.0);
730 assert_eq!(result.duration_seconds, 180);
731 assert_eq!(
732 result.geometry,
733 vec![
734 Coord::new(0.0, 0.0),
735 Coord::new(0.01, 0.0),
736 Coord::new(0.01, 0.02),
737 ]
738 );
739 assert!(network.distance_lower_bound_between(NodeIdx(0), NodeIdx(3)) > 0.0);
740 }
741}
742
743#[derive(Debug, Clone, Copy, PartialEq, Eq)]
744pub enum Objective {
745 Time,
746 Distance,
747}
748
749fn douglas_peucker(points: &[Coord], tolerance_m: f64) -> Vec<Coord> {
751 if points.len() <= 2 {
752 return points.to_vec();
753 }
754
755 let first = points[0];
756 let last = points[points.len() - 1];
757 let mut max_dist = 0.0;
758 let mut max_idx = 0;
759
760 for (i, point) in points.iter().enumerate().skip(1).take(points.len() - 2) {
761 let dist = perpendicular_distance(*point, first, last);
762 if dist > max_dist {
763 max_dist = dist;
764 max_idx = i;
765 }
766 }
767
768 if max_dist > tolerance_m {
769 let mut left = douglas_peucker(&points[..=max_idx], tolerance_m);
770 let right = douglas_peucker(&points[max_idx..], tolerance_m);
771
772 left.pop();
773 left.extend(right);
774 left
775 } else {
776 vec![first, last]
777 }
778}
779
780fn perpendicular_distance(point: Coord, line_start: Coord, line_end: Coord) -> f64 {
781 let dx = line_end.lng - line_start.lng;
782 let dy = line_end.lat - line_start.lat;
783
784 let line_length_sq = dx * dx + dy * dy;
785 if line_length_sq < f64::EPSILON {
786 return haversine_distance(point, line_start);
787 }
788
789 let t =
790 ((point.lng - line_start.lng) * dx + (point.lat - line_start.lat) * dy) / line_length_sq;
791 let t = t.clamp(0.0, 1.0);
792
793 let projected = Coord::new(line_start.lat + t * dy, line_start.lng + t * dx);
794
795 haversine_distance(point, projected)
796}