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
88pub struct RoadNetwork {
89 pub(super) graph: DiGraph<NodeData, EdgeData>,
90 pub(super) coord_to_node: HashMap<(i64, i64), NodeIdx>,
91 spatial_index: Option<KdTree<SpatialPoint>>,
92 edge_spatial_index: Option<SegmentIndex<SpatialSegment>>,
93}
94
95impl RoadNetwork {
96 pub fn new() -> Self {
97 Self {
98 graph: DiGraph::new(),
99 coord_to_node: HashMap::new(),
100 spatial_index: None,
101 edge_spatial_index: None,
102 }
103 }
104
105 pub(super) fn get_or_create_node(&mut self, lat: f64, lng: f64) -> NodeIdx {
106 let key = coord_key(lat, lng);
107 if let Some(&idx) = self.coord_to_node.get(&key) {
108 idx
109 } else {
110 let idx = self.graph.add_node(NodeData { lat, lng });
111 self.coord_to_node.insert(key, idx);
112 idx
113 }
114 }
115
116 pub(super) fn add_node_at(&mut self, lat: f64, lng: f64) -> NodeIdx {
117 let idx = self.graph.add_node(NodeData { lat, lng });
118 let key = coord_key(lat, lng);
119 self.coord_to_node.insert(key, idx);
120 idx
121 }
122
123 pub(super) fn add_edge(&mut self, from: NodeIdx, to: NodeIdx, data: EdgeData) {
124 self.graph.add_edge(from, to, data);
125 }
126
127 pub(super) fn add_edge_by_index(
128 &mut self,
129 from: usize,
130 to: usize,
131 travel_time_s: f64,
132 distance_m: f64,
133 ) {
134 let from_idx = NodeIdx::new(from);
135 let to_idx = NodeIdx::new(to);
136 self.graph.add_edge(
137 from_idx,
138 to_idx,
139 EdgeData {
140 travel_time_s,
141 distance_m,
142 },
143 );
144 }
145
146 pub(super) fn build_spatial_index(&mut self) {
147 let points: Vec<(Point2D, SpatialPoint)> = self
149 .graph
150 .node_indices()
151 .filter_map(|node_idx| {
152 let node = self.graph.node_weight(node_idx)?;
153 Some((
154 Point2D::new(node.lat, node.lng),
155 SpatialPoint {
156 node_index: node_idx,
157 },
158 ))
159 })
160 .collect();
161
162 self.spatial_index = Some(KdTree::from_items(points));
163
164 let segments: Vec<(Segment, SpatialSegment)> = self
166 .graph
167 .edge_indices()
168 .filter_map(|edge_idx| {
169 let (from_node, to_node) = self.graph.edge_endpoints(edge_idx)?;
170 let from_data = self.graph.node_weight(from_node)?;
171 let to_data = self.graph.node_weight(to_node)?;
172 Some((
173 Segment::new(
174 Point2D::new(from_data.lat, from_data.lng),
175 Point2D::new(to_data.lat, to_data.lng),
176 ),
177 SpatialSegment {
178 from_node,
179 to_node,
180 edge_index: edge_idx,
181 },
182 ))
183 })
184 .collect();
185
186 self.edge_spatial_index = Some(SegmentIndex::bulk_load(segments));
187 }
188
189 pub fn nodes_iter(&self) -> impl Iterator<Item = (f64, f64)> + '_ {
191 self.graph
192 .node_indices()
193 .filter_map(|idx| self.graph.node_weight(idx).map(|n| (n.lat, n.lng)))
194 }
195
196 pub fn edges_iter(&self) -> impl Iterator<Item = (usize, usize, f64, f64)> + '_ {
198 self.graph.edge_indices().filter_map(|idx| {
199 let (from, to) = self.graph.edge_endpoints(idx)?;
200 let weight = self.graph.edge_weight(idx)?;
201 Some((
202 from.index(),
203 to.index(),
204 weight.travel_time_s,
205 weight.distance_m,
206 ))
207 })
208 }
209
210 pub fn snap_to_road(&self, coord: Coord) -> Option<NodeIdx> {
214 self.spatial_index
215 .as_ref()?
216 .nearest_neighbor(&Point2D::new(coord.lat, coord.lng))
217 .map(|p| p.node_index)
218 }
219
220 pub fn snap_to_road_detailed(&self, coord: Coord) -> Result<SnappedCoord, RoutingError> {
225 let tree = self
226 .spatial_index
227 .as_ref()
228 .ok_or(RoutingError::SnapFailed {
229 coord,
230 nearest_distance_m: None,
231 })?;
232
233 let query = Point2D::new(coord.lat, coord.lng);
234 let (nearest, _dist_sq) =
235 tree.nearest_neighbor_with_distance(&query)
236 .ok_or(RoutingError::SnapFailed {
237 coord,
238 nearest_distance_m: None,
239 })?;
240
241 let node_data =
243 self.graph
244 .node_weight(nearest.node_index)
245 .ok_or(RoutingError::SnapFailed {
246 coord,
247 nearest_distance_m: None,
248 })?;
249
250 let snapped = Coord::new(node_data.lat, node_data.lng);
251 let snap_distance_m = haversine_distance(coord, snapped);
252
253 Ok(SnappedCoord {
254 original: coord,
255 snapped,
256 snap_distance_m,
257 node_index: nearest.node_index,
258 })
259 }
260
261 pub fn snap_to_edge(&self, coord: Coord) -> Result<EdgeSnappedLocation, RoutingError> {
266 let index = self
267 .edge_spatial_index
268 .as_ref()
269 .ok_or(RoutingError::SnapFailed {
270 coord,
271 nearest_distance_m: None,
272 })?;
273
274 let query = Point2D::new(coord.lat, coord.lng);
275 let (segment, seg_data, proj_point, _dist_sq) =
276 index
277 .nearest_segment(&query)
278 .ok_or(RoutingError::SnapFailed {
279 coord,
280 nearest_distance_m: None,
281 })?;
282
283 let (_, position) = segment.project_point(&query);
285 let snapped = Coord::new(proj_point.x, proj_point.y);
286 let snap_distance_m = haversine_distance(coord, snapped);
287
288 Ok(EdgeSnappedLocation {
289 original: coord,
290 snapped,
291 snap_distance_m,
292 edge_index: seg_data.edge_index,
293 position,
294 from_node: seg_data.from_node,
295 to_node: seg_data.to_node,
296 })
297 }
298
299 pub fn route(&self, from: Coord, to: Coord) -> Result<RouteResult, RoutingError> {
304 let start_snap = self.snap_to_road_detailed(from)?;
305 let end_snap = self.snap_to_road_detailed(to)?;
306
307 self.route_snapped(&start_snap, &end_snap)
308 }
309
310 pub fn route_edge_snapped(
315 &self,
316 from: &EdgeSnappedLocation,
317 to: &EdgeSnappedLocation,
318 ) -> Result<RouteResult, RoutingError> {
319 let from_edge = self
320 .graph
321 .edge_weight(from.edge_index)
322 .ok_or(RoutingError::NoPath {
323 from: from.original,
324 to: to.original,
325 })?;
326 let to_edge = self
327 .graph
328 .edge_weight(to.edge_index)
329 .ok_or(RoutingError::NoPath {
330 from: from.original,
331 to: to.original,
332 })?;
333
334 if from.edge_index == to.edge_index {
336 let segment_time = from_edge.travel_time_s;
337 let segment_dist = from_edge.distance_m;
338 let travel_fraction = (to.position - from.position).abs();
339 return Ok(RouteResult {
340 duration_seconds: (segment_time * travel_fraction).round() as i64,
341 distance_meters: segment_dist * travel_fraction,
342 geometry: vec![from.snapped, to.snapped],
343 });
344 }
345
346 let cost_to_from_node = from_edge.travel_time_s * from.position;
348 let cost_to_to_node = from_edge.travel_time_s * (1.0 - from.position);
350
351 let cost_from_dest_from = to_edge.travel_time_s * to.position;
353 let cost_from_dest_to = to_edge.travel_time_s * (1.0 - to.position);
355
356 let mut best_result: Option<(f64, Vec<NodeIdx>, NodeIdx, f64)> = None;
358
359 for &(start_node, start_cost) in &[
360 (from.from_node, cost_to_from_node),
361 (from.to_node, cost_to_to_node),
362 ] {
363 for &(end_node, end_cost) in &[
364 (to.from_node, cost_from_dest_from),
365 (to.to_node, cost_from_dest_to),
366 ] {
367 if start_node == end_node {
368 let total_cost = start_cost + end_cost;
369 if best_result.is_none() || total_cost < best_result.as_ref().unwrap().0 {
370 best_result = Some((total_cost, vec![start_node], end_node, end_cost));
371 }
372 continue;
373 }
374
375 let result = astar(
376 &self.graph,
377 start_node,
378 |n| n == end_node,
379 |e| e.travel_time_s,
380 |_| 0.0,
381 );
382
383 if let Some((path_cost, path)) = result {
384 let total_cost = start_cost + path_cost + end_cost;
385 if best_result.is_none() || total_cost < best_result.as_ref().unwrap().0 {
386 best_result = Some((total_cost, path, end_node, end_cost));
387 }
388 }
389 }
390 }
391
392 match best_result {
393 Some((total_cost, path, _, _)) => {
394 let mut geometry = vec![from.snapped];
395 for &idx in &path {
396 if let Some(node) = self.graph.node_weight(idx) {
397 geometry.push(node.coord());
398 }
399 }
400 geometry.push(to.snapped);
401
402 let mut distance = 0.0;
403 distance += from_edge.distance_m * from.position.min(1.0 - from.position);
404 for window in path.windows(2) {
405 if let Some(edge) = self.graph.find_edge(window[0], window[1]) {
406 if let Some(weight) = self.graph.edge_weight(edge) {
407 distance += weight.distance_m;
408 }
409 }
410 }
411
412 distance += to_edge.distance_m * to.position.min(1.0 - to.position);
413
414 Ok(RouteResult {
415 duration_seconds: total_cost.round() as i64,
416 distance_meters: distance,
417 geometry,
418 })
419 }
420 None => Err(RoutingError::NoPath {
421 from: from.original,
422 to: to.original,
423 }),
424 }
425 }
426
427 pub fn route_snapped(
428 &self,
429 from: &SnappedCoord,
430 to: &SnappedCoord,
431 ) -> Result<RouteResult, RoutingError> {
432 if from.node_index == to.node_index {
433 return Ok(RouteResult {
434 duration_seconds: 0,
435 distance_meters: 0.0,
436 geometry: vec![from.original, to.original],
437 });
438 }
439
440 let result = astar(
441 &self.graph,
442 from.node_index,
443 |n| n == to.node_index,
444 |e| e.travel_time_s,
445 |_| 0.0,
446 );
447
448 match result {
449 Some((cost, path)) => {
450 let geometry: Vec<Coord> = path
451 .iter()
452 .filter_map(|&idx| self.graph.node_weight(idx).map(|n| n.coord()))
453 .collect();
454
455 let mut distance = 0.0;
456 for window in path.windows(2) {
457 if let Some(edge) = self.graph.find_edge(window[0], window[1]) {
458 if let Some(weight) = self.graph.edge_weight(edge) {
459 distance += weight.distance_m;
460 }
461 }
462 }
463
464 Ok(RouteResult {
465 duration_seconds: 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_with(
478 &self,
479 from: Coord,
480 to: Coord,
481 objective: Objective,
482 ) -> Result<RouteResult, RoutingError> {
483 let start_snap = self.snap_to_road_detailed(from)?;
484 let end_snap = self.snap_to_road_detailed(to)?;
485
486 if start_snap.node_index == end_snap.node_index {
487 return Ok(RouteResult {
488 duration_seconds: 0,
489 distance_meters: 0.0,
490 geometry: vec![from, to],
491 });
492 }
493
494 let result = match objective {
495 Objective::Time => astar(
496 &self.graph,
497 start_snap.node_index,
498 |n| n == end_snap.node_index,
499 |e| e.travel_time_s,
500 |_| 0.0,
501 ),
502 Objective::Distance => astar(
503 &self.graph,
504 start_snap.node_index,
505 |n| n == end_snap.node_index,
506 |e| e.distance_m,
507 |_| 0.0,
508 ),
509 };
510
511 match result {
512 Some((_, path)) => {
513 let geometry: Vec<Coord> = path
514 .iter()
515 .filter_map(|&idx| self.graph.node_weight(idx).map(|n| n.coord()))
516 .collect();
517
518 let mut distance = 0.0;
519 let mut time = 0.0;
520 for window in path.windows(2) {
521 if let Some(edge) = self.graph.find_edge(window[0], window[1]) {
522 if let Some(weight) = self.graph.edge_weight(edge) {
523 distance += weight.distance_m;
524 time += weight.travel_time_s;
525 }
526 }
527 }
528
529 Ok(RouteResult {
530 duration_seconds: time.round() as i64,
531 distance_meters: distance,
532 geometry,
533 })
534 }
535 None => Err(RoutingError::NoPath { from, to }),
536 }
537 }
538
539 pub fn node_count(&self) -> usize {
540 self.graph.node_count()
541 }
542
543 pub fn edge_count(&self) -> usize {
544 self.graph.edge_count()
545 }
546
547 pub fn strongly_connected_components(&self) -> usize {
548 kosaraju_scc(&self.graph).len()
549 }
550
551 pub fn largest_component_fraction(&self) -> f64 {
552 let sccs = kosaraju_scc(&self.graph);
553 if sccs.is_empty() {
554 return 0.0;
555 }
556 let largest = sccs.iter().map(|c| c.len()).max().unwrap_or(0);
557 let total = self.graph.node_count();
558 if total == 0 {
559 0.0
560 } else {
561 largest as f64 / total as f64
562 }
563 }
564
565 pub fn is_strongly_connected(&self) -> bool {
566 self.strongly_connected_components() == 1
567 }
568
569 pub fn filter_to_largest_scc(&mut self) {
571 let sccs = kosaraju_scc(&self.graph);
572 if sccs.len() <= 1 {
573 return; }
575
576 let largest_scc: HashSet<NodeIdx> = sccs
578 .into_iter()
579 .max_by_key(|scc| scc.len())
580 .unwrap_or_default()
581 .into_iter()
582 .collect();
583
584 self.graph.retain_nodes(|n, _| largest_scc.contains(&n));
586
587 self.rebuild_coord_to_node();
588 self.build_spatial_index();
589 }
590
591 fn rebuild_coord_to_node(&mut self) {
592 self.coord_to_node.clear();
593 for idx in self.graph.node_indices() {
594 if let Some(node) = self.graph.node_weight(idx) {
595 let key = coord_key(node.lat, node.lng);
596 self.coord_to_node.insert(key, idx);
597 }
598 }
599 }
600}
601
602impl Default for RoadNetwork {
603 fn default() -> Self {
604 Self::new()
605 }
606}
607
608impl RoadNetwork {
609 pub fn from_test_data(nodes: &[(f64, f64)], edges: &[(usize, usize, f64, f64)]) -> Self {
627 let mut network = Self::new();
628
629 for &(lat, lng) in nodes {
631 network.add_node_at(lat, lng);
632 }
633
634 for &(from, to, time_s, dist_m) in edges {
636 network.add_edge_by_index(from, to, time_s, dist_m);
637 }
638
639 network.build_spatial_index();
641
642 network
643 }
644}
645
646#[derive(Debug, Clone, Copy, PartialEq, Eq)]
647pub enum Objective {
648 Time,
649 Distance,
650}
651
652fn douglas_peucker(points: &[Coord], tolerance_m: f64) -> Vec<Coord> {
654 if points.len() <= 2 {
655 return points.to_vec();
656 }
657
658 let first = points[0];
659 let last = points[points.len() - 1];
660 let mut max_dist = 0.0;
661 let mut max_idx = 0;
662
663 for (i, point) in points.iter().enumerate().skip(1).take(points.len() - 2) {
664 let dist = perpendicular_distance(*point, first, last);
665 if dist > max_dist {
666 max_dist = dist;
667 max_idx = i;
668 }
669 }
670
671 if max_dist > tolerance_m {
672 let mut left = douglas_peucker(&points[..=max_idx], tolerance_m);
673 let right = douglas_peucker(&points[max_idx..], tolerance_m);
674
675 left.pop();
676 left.extend(right);
677 left
678 } else {
679 vec![first, last]
680 }
681}
682
683fn perpendicular_distance(point: Coord, line_start: Coord, line_end: Coord) -> f64 {
684 let dx = line_end.lng - line_start.lng;
685 let dy = line_end.lat - line_start.lat;
686
687 let line_length_sq = dx * dx + dy * dy;
688 if line_length_sq < f64::EPSILON {
689 return haversine_distance(point, line_start);
690 }
691
692 let t =
693 ((point.lng - line_start.lng) * dx + (point.lat - line_start.lat) * dy) / line_length_sq;
694 let t = t.clamp(0.0, 1.0);
695
696 let projected = Coord::new(line_start.lat + t * dy, line_start.lng + t * dx);
697
698 haversine_distance(point, projected)
699}