@@ -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
102103impl 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 ) ]
653757pub enum Objective {
654758 Time ,
0 commit comments