Skip to content

Commit 6f609ac

Browse files
authored
Merge pull request #19 from SolverForge/issue/6-add-real-routing-heuristic
routing: add a real heuristic to public route search
2 parents 91a86df + 1ec700e commit 6f609ac

1 file changed

Lines changed: 118 additions & 14 deletions

File tree

src/routing/network.rs

Lines changed: 118 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub struct RoadNetwork {
9797
pub(super) coord_to_node: HashMap<(i64, i64), NodeIdx>,
9898
spatial_index: Option<KdTree<SpatialPoint>>,
9999
edge_spatial_index: Option<SegmentIndex<SpatialSegment>>,
100+
max_speed_mps: f64,
100101
}
101102

102103
impl RoadNetwork {
@@ -106,6 +107,7 @@ impl RoadNetwork {
106107
coord_to_node: HashMap::new(),
107108
spatial_index: None,
108109
edge_spatial_index: None,
110+
max_speed_mps: 0.0,
109111
}
110112
}
111113

@@ -128,6 +130,7 @@ impl RoadNetwork {
128130
}
129131

130132
pub(super) fn add_edge(&mut self, from: NodeIdx, to: NodeIdx, data: EdgeData) {
133+
self.record_edge_speed(&data);
131134
self.graph.add_edge(from, to, data);
132135
}
133136

@@ -140,14 +143,12 @@ impl RoadNetwork {
140143
) {
141144
let from_idx = NodeIdx::new(from);
142145
let to_idx = NodeIdx::new(to);
143-
self.graph.add_edge(
144-
from_idx,
145-
to_idx,
146-
EdgeData {
147-
travel_time_s,
148-
distance_m,
149-
},
150-
);
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);
151152
}
152153

153154
pub(super) fn build_spatial_index(&mut self) {
@@ -193,6 +194,35 @@ impl RoadNetwork {
193194
self.edge_spatial_index = Some(SegmentIndex::bulk_load(segments));
194195
}
195196

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+
196226
/// Iterate over all nodes as (lat, lng) pairs.
197227
pub fn nodes_iter(&self) -> impl Iterator<Item = (f64, f64)> + '_ {
198228
self.graph
@@ -378,7 +408,7 @@ impl RoadNetwork {
378408
start_exit.node,
379409
|n| n == end_entry.node,
380410
|e| e.travel_time_s,
381-
|_| 0.0,
411+
|n| self.time_lower_bound_between(n, end_entry.node),
382412
)
383413
.map(|(path_cost, path)| (start_exit.time_s + path_cost + end_entry.time_s, path))
384414
};
@@ -444,7 +474,7 @@ impl RoadNetwork {
444474
from.node_index,
445475
|n| n == to.node_index,
446476
|e| e.travel_time_s,
447-
|_| 0.0,
477+
|n| self.time_lower_bound_between(n, to.node_index),
448478
);
449479

450480
match result {
@@ -478,8 +508,8 @@ impl RoadNetwork {
478508

479509
/// Find a route between two coordinates with an explicit optimization objective.
480510
///
481-
/// Like `route`, this method snaps to the nearest graph nodes first. The
482-
/// current public search still uses a zero heuristic for both objectives.
511+
/// Like `route`, this method snaps to the nearest graph nodes first and
512+
/// uses admissible straight-line lower bounds for both objectives.
483513
pub fn route_with(
484514
&self,
485515
from: Coord,
@@ -503,14 +533,14 @@ impl RoadNetwork {
503533
start_snap.node_index,
504534
|n| n == end_snap.node_index,
505535
|e| e.travel_time_s,
506-
|_| 0.0,
536+
|n| self.time_lower_bound_between(n, end_snap.node_index),
507537
),
508538
Objective::Distance => astar(
509539
&self.graph,
510540
start_snap.node_index,
511541
|n| n == end_snap.node_index,
512542
|e| e.distance_m,
513-
|_| 0.0,
543+
|n| self.distance_lower_bound_between(n, end_snap.node_index),
514544
),
515545
};
516546

@@ -649,6 +679,80 @@ impl RoadNetwork {
649679
}
650680
}
651681

682+
#[cfg(test)]
683+
mod tests {
684+
use super::{NodeIdx, Objective, RoadNetwork};
685+
use crate::routing::Coord;
686+
687+
#[test]
688+
fn time_routing_uses_non_zero_admissible_heuristic() {
689+
let nodes = &[(0.0, 0.0), (0.0, 0.01), (0.01, 0.0), (0.01, 0.01)];
690+
let edges = &[
691+
(0, 1, 200.0, 1_200.0),
692+
(1, 3, 200.0, 1_200.0),
693+
(0, 2, 50.0, 1_200.0),
694+
(2, 3, 50.0, 1_200.0),
695+
(1, 0, 200.0, 1_200.0),
696+
(3, 1, 200.0, 1_200.0),
697+
(2, 0, 50.0, 1_200.0),
698+
(3, 2, 50.0, 1_200.0),
699+
];
700+
let network = RoadNetwork::from_test_data(nodes, edges);
701+
702+
let result = network
703+
.route(Coord::new(0.0, 0.0), Coord::new(0.01, 0.01))
704+
.expect("time route should exist");
705+
706+
assert_eq!(result.duration_seconds, 100);
707+
assert_eq!(result.distance_meters, 2_400.0);
708+
assert_eq!(
709+
result.geometry,
710+
vec![
711+
Coord::new(0.0, 0.0),
712+
Coord::new(0.01, 0.0),
713+
Coord::new(0.01, 0.01),
714+
]
715+
);
716+
assert!(network.time_lower_bound_between(NodeIdx(0), NodeIdx(3)) > 0.0);
717+
}
718+
719+
#[test]
720+
fn distance_routing_uses_non_zero_admissible_heuristic() {
721+
let nodes = &[(0.0, 0.0), (0.0, 0.02), (0.01, 0.0), (0.01, 0.02)];
722+
let edges = &[
723+
(0, 1, 40.0, 3_000.0),
724+
(1, 3, 40.0, 3_000.0),
725+
(0, 2, 90.0, 900.0),
726+
(2, 3, 90.0, 900.0),
727+
(1, 0, 40.0, 3_000.0),
728+
(3, 1, 40.0, 3_000.0),
729+
(2, 0, 90.0, 900.0),
730+
(3, 2, 90.0, 900.0),
731+
];
732+
let network = RoadNetwork::from_test_data(nodes, edges);
733+
734+
let result = network
735+
.route_with(
736+
Coord::new(0.0, 0.0),
737+
Coord::new(0.01, 0.02),
738+
Objective::Distance,
739+
)
740+
.expect("distance route should exist");
741+
742+
assert_eq!(result.distance_meters, 1_800.0);
743+
assert_eq!(result.duration_seconds, 180);
744+
assert_eq!(
745+
result.geometry,
746+
vec![
747+
Coord::new(0.0, 0.0),
748+
Coord::new(0.01, 0.0),
749+
Coord::new(0.01, 0.02),
750+
]
751+
);
752+
assert!(network.distance_lower_bound_between(NodeIdx(0), NodeIdx(3)) > 0.0);
753+
}
754+
}
755+
652756
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
653757
pub enum Objective {
654758
Time,

0 commit comments

Comments
 (0)