Skip to content

Commit e2d7b89

Browse files
authored
Merge pull request #17 from SolverForge/issue/4-remove-hard-default-scc-filter
routing: remove largest-SCC filtering as a hard default
2 parents d142abe + fafc71e commit e2d7b89

File tree

5 files changed

+102
-34
lines changed

5 files changed

+102
-34
lines changed

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub mod routing;
3030

3131
pub use geometry::{decode_polyline, encode_polyline, EncodedSegment};
3232
pub use routing::{
33-
haversine_distance, BBoxError, BoundingBox, CacheStats, Coord, CoordError, NetworkConfig,
34-
NetworkRef, Objective, RoadNetwork, RouteResult, RoutingError, RoutingProgress, RoutingResult,
35-
SnappedCoord, SpeedProfile, TravelTimeMatrix, UNREACHABLE,
33+
haversine_distance, BBoxError, BoundingBox, CacheStats, ConnectivityPolicy, Coord, CoordError,
34+
NetworkConfig, NetworkRef, Objective, RoadNetwork, RouteResult, RoutingError, RoutingProgress,
35+
RoutingResult, SnappedCoord, SpeedProfile, TravelTimeMatrix, UNREACHABLE,
3636
};

src/routing/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
use std::path::PathBuf;
44
use std::time::Duration;
55

6+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7+
pub enum ConnectivityPolicy {
8+
KeepAll,
9+
LargestStronglyConnectedComponent,
10+
}
11+
612
#[derive(Debug, Clone)]
713
pub struct SpeedProfile {
814
pub motorway: f64,
@@ -92,6 +98,7 @@ pub struct NetworkConfig {
9298
pub connect_timeout: Duration,
9399
pub read_timeout: Duration,
94100
pub speed_profile: SpeedProfile,
101+
pub connectivity_policy: ConnectivityPolicy,
95102
pub highway_types: Vec<&'static str>,
96103
}
97104

@@ -103,6 +110,7 @@ impl Default for NetworkConfig {
103110
connect_timeout: Duration::from_secs(30),
104111
read_timeout: Duration::from_secs(180),
105112
speed_profile: SpeedProfile::default(),
113+
connectivity_policy: ConnectivityPolicy::KeepAll,
106114
highway_types: vec![
107115
"motorway",
108116
"trunk",
@@ -148,6 +156,11 @@ impl NetworkConfig {
148156
self
149157
}
150158

159+
pub fn connectivity_policy(mut self, policy: ConnectivityPolicy) -> Self {
160+
self.connectivity_policy = policy;
161+
self
162+
}
163+
151164
pub fn highway_types(mut self, types: Vec<&'static str>) -> Self {
152165
self.highway_types = types;
153166
self

src/routing/fetch.rs

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use super::cache::{
1111
cache, record_hit, record_miss, CachedEdge, CachedNetwork, CachedNode, NetworkRef,
1212
CACHE_VERSION,
1313
};
14-
use super::config::NetworkConfig;
14+
use super::config::{ConnectivityPolicy, NetworkConfig};
1515
use super::coord::Coord;
1616
use super::error::RoutingError;
1717
use super::network::{EdgeData, RoadNetwork};
@@ -60,7 +60,7 @@ impl RoadNetwork {
6060
if let Some(tx) = progress {
6161
let _ = tx.send(RoutingProgress::CheckingCache { percent: 8 }).await;
6262
}
63-
match Self::load_from_file(&cache_path).await {
63+
match Self::load_from_file(&cache_path, config).await {
6464
Ok(n) => {
6565
if let Some(tx) = progress {
6666
let _ = tx
@@ -314,27 +314,38 @@ out body;"#,
314314
way_count
315315
);
316316

317-
// Filter to largest strongly connected component to ensure all nodes are reachable
318317
let scc_count = network.strongly_connected_components();
319-
if scc_count > 1 {
320-
info!(
321-
"Road network has {} SCCs, filtering to largest component",
322-
scc_count
323-
);
324-
network.filter_to_largest_scc();
325-
info!(
326-
"After SCC filter: {} nodes, {} edges",
327-
network.node_count(),
328-
network.edge_count()
329-
);
318+
match config.connectivity_policy {
319+
ConnectivityPolicy::KeepAll => {
320+
if scc_count > 1 {
321+
info!(
322+
"Road network has {} SCCs, preserving all components by configuration",
323+
scc_count
324+
);
325+
}
326+
}
327+
ConnectivityPolicy::LargestStronglyConnectedComponent => {
328+
if scc_count > 1 {
329+
info!(
330+
"Road network has {} SCCs, filtering to largest component",
331+
scc_count
332+
);
333+
network.filter_to_largest_scc();
334+
info!(
335+
"After SCC filter: {} nodes, {} edges",
336+
network.node_count(),
337+
network.edge_count()
338+
);
339+
}
340+
}
330341
}
331342

332343
network.build_spatial_index();
333344

334345
Ok(network)
335346
}
336347

337-
async fn load_from_file(path: &Path) -> Result<Self, RoutingError> {
348+
async fn load_from_file(path: &Path, config: &NetworkConfig) -> Result<Self, RoutingError> {
338349
let data = tokio::fs::read_to_string(path).await?;
339350

340351
let cached: CachedNetwork = match serde_json::from_str(&data) {
@@ -365,19 +376,30 @@ out body;"#,
365376
network.add_edge_by_index(edge.from, edge.to, edge.travel_time_s, edge.distance_m);
366377
}
367378

368-
// Filter to largest SCC (cached networks from older versions may not be filtered)
369379
let scc_count = network.strongly_connected_components();
370-
if scc_count > 1 {
371-
info!(
372-
"Cached network has {} SCCs, filtering to largest component",
373-
scc_count
374-
);
375-
network.filter_to_largest_scc();
376-
info!(
377-
"After SCC filter: {} nodes, {} edges",
378-
network.node_count(),
379-
network.edge_count()
380-
);
380+
match config.connectivity_policy {
381+
ConnectivityPolicy::KeepAll => {
382+
if scc_count > 1 {
383+
info!(
384+
"Cached network has {} SCCs, preserving all components by configuration",
385+
scc_count
386+
);
387+
}
388+
}
389+
ConnectivityPolicy::LargestStronglyConnectedComponent => {
390+
if scc_count > 1 {
391+
info!(
392+
"Cached network has {} SCCs, filtering to largest component",
393+
scc_count
394+
);
395+
network.filter_to_largest_scc();
396+
info!(
397+
"After SCC filter: {} nodes, {} edges",
398+
network.node_count(),
399+
network.edge_count()
400+
);
401+
}
402+
}
381403
}
382404

383405
network.build_spatial_index();

src/routing/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod spatial;
1717

1818
pub use bbox::BoundingBox;
1919
pub use cache::{CacheStats, NetworkRef};
20-
pub use config::{NetworkConfig, SpeedProfile};
20+
pub use config::{ConnectivityPolicy, NetworkConfig, SpeedProfile};
2121
pub use coord::Coord;
2222
pub use error::{BBoxError, CoordError, RoutingError};
2323
pub use matrix::{TravelTimeMatrix, UNREACHABLE};

tests/integration.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use std::path::PathBuf;
44
use std::time::Duration;
55

66
use solverforge_maps::{
7-
decode_polyline, encode_polyline, haversine_distance, BBoxError, BoundingBox, Coord,
8-
CoordError, NetworkConfig, RoadNetwork, RouteResult, RoutingError, SpeedProfile, UNREACHABLE,
7+
decode_polyline, encode_polyline, haversine_distance, BBoxError, BoundingBox,
8+
ConnectivityPolicy, Coord, CoordError, NetworkConfig, RoadNetwork, RouteResult, RoutingError,
9+
SpeedProfile, UNREACHABLE,
910
};
1011

1112
mod types {
@@ -140,11 +141,16 @@ mod types {
140141
let config = NetworkConfig::new()
141142
.overpass_url("https://custom.api/interpreter")
142143
.cache_dir("/tmp/cache")
143-
.connect_timeout(Duration::from_secs(60));
144+
.connect_timeout(Duration::from_secs(60))
145+
.connectivity_policy(ConnectivityPolicy::LargestStronglyConnectedComponent);
144146

145147
assert_eq!(config.overpass_url, "https://custom.api/interpreter");
146148
assert_eq!(config.cache_dir, PathBuf::from("/tmp/cache"));
147149
assert_eq!(config.connect_timeout, Duration::from_secs(60));
150+
assert_eq!(
151+
config.connectivity_policy,
152+
ConnectivityPolicy::LargestStronglyConnectedComponent
153+
);
148154
}
149155

150156
#[test]
@@ -156,6 +162,12 @@ mod types {
156162
let maxspeed_mps = profile.speed_mps(Some("50"), "motorway");
157163
assert!((maxspeed_mps - 13.889).abs() < 0.1);
158164
}
165+
166+
#[test]
167+
fn default_connectivity_policy_keeps_all_components() {
168+
let config = NetworkConfig::default();
169+
assert_eq!(config.connectivity_policy, ConnectivityPolicy::KeepAll);
170+
}
159171
}
160172
}
161173

@@ -242,6 +254,27 @@ mod routing {
242254
assert_eq!(network.strongly_connected_components(), 0);
243255
assert!((network.largest_component_fraction() - 0.0).abs() < f64::EPSILON);
244256
}
257+
258+
#[test]
259+
fn largest_scc_filter_is_opt_in() {
260+
let mut network = RoadNetwork::from_test_data(
261+
&[(0.0, 0.0), (0.0, 1.0), (10.0, 10.0), (10.0, 11.0)],
262+
&[
263+
(0, 1, 10.0, 100.0),
264+
(1, 0, 10.0, 100.0),
265+
(2, 3, 10.0, 100.0),
266+
],
267+
);
268+
269+
assert_eq!(network.node_count(), 4);
270+
assert_eq!(network.strongly_connected_components(), 3);
271+
272+
network.filter_to_largest_scc();
273+
274+
assert_eq!(network.node_count(), 2);
275+
assert_eq!(network.edge_count(), 2);
276+
assert_eq!(network.strongly_connected_components(), 1);
277+
}
245278
}
246279

247280
mod route_simplify {

0 commit comments

Comments
 (0)