Skip to main content

solverforge_maps/routing/
config.rs

1//! Configuration for road network operations.
2
3use std::path::PathBuf;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ConnectivityPolicy {
8    KeepAll,
9    LargestStronglyConnectedComponent,
10}
11
12#[derive(Debug, Clone)]
13pub struct SpeedProfile {
14    pub motorway: f64,
15    pub trunk: f64,
16    pub primary: f64,
17    pub secondary: f64,
18    pub tertiary: f64,
19    pub residential: f64,
20    pub unclassified: f64,
21    pub service: f64,
22    pub living_street: f64,
23    pub default: f64,
24}
25
26impl Default for SpeedProfile {
27    fn default() -> Self {
28        Self {
29            motorway: 100.0,
30            trunk: 80.0,
31            primary: 60.0,
32            secondary: 50.0,
33            tertiary: 40.0,
34            residential: 30.0,
35            unclassified: 30.0,
36            service: 20.0,
37            living_street: 10.0,
38            default: 30.0,
39        }
40    }
41}
42
43impl SpeedProfile {
44    pub fn speed_mps(&self, maxspeed: Option<&str>, highway: &str) -> f64 {
45        let kmh = maxspeed
46            .and_then(|v| self.parse_maxspeed(v))
47            .unwrap_or_else(|| self.highway_speed(highway));
48        kmh * 1000.0 / 3600.0
49    }
50
51    fn highway_speed(&self, highway: &str) -> f64 {
52        match highway {
53            "motorway" | "motorway_link" => self.motorway,
54            "trunk" | "trunk_link" => self.trunk,
55            "primary" | "primary_link" => self.primary,
56            "secondary" | "secondary_link" => self.secondary,
57            "tertiary" | "tertiary_link" => self.tertiary,
58            "residential" => self.residential,
59            "unclassified" => self.unclassified,
60            "service" => self.service,
61            "living_street" => self.living_street,
62            _ => self.default,
63        }
64    }
65
66    fn parse_maxspeed(&self, value: &str) -> Option<f64> {
67        let value = value.trim();
68
69        match value.to_lowercase().as_str() {
70            "walk" => return Some(5.0),
71            "none" | "unlimited" => return None,
72            _ => {}
73        }
74
75        let (num_str, is_mph) = if let Some(s) = value.strip_suffix("mph") {
76            (s.trim(), true)
77        } else if let Some(s) = value.strip_suffix("km/h") {
78            (s.trim(), false)
79        } else if let Some(s) = value.strip_suffix("kmh") {
80            (s.trim(), false)
81        } else {
82            (value, false)
83        };
84
85        let kmh: f64 = num_str.parse().ok()?;
86        if kmh <= 0.0 || kmh > 300.0 {
87            return None;
88        }
89
90        Some(if is_mph { kmh * 1.60934 } else { kmh })
91    }
92}
93
94#[derive(Debug, Clone)]
95pub struct NetworkConfig {
96    pub overpass_url: String,
97    pub overpass_endpoints: Vec<String>,
98    pub overpass_max_retries: usize,
99    pub overpass_retry_backoff: Duration,
100    pub cache_dir: PathBuf,
101    pub connect_timeout: Duration,
102    pub read_timeout: Duration,
103    pub speed_profile: SpeedProfile,
104    pub connectivity_policy: ConnectivityPolicy,
105    pub highway_types: Vec<&'static str>,
106}
107
108impl Default for NetworkConfig {
109    fn default() -> Self {
110        let default_overpass = "https://overpass-api.de/api/interpreter".to_string();
111        Self {
112            overpass_url: default_overpass.clone(),
113            overpass_endpoints: vec![default_overpass],
114            overpass_max_retries: 2,
115            overpass_retry_backoff: Duration::from_secs(2),
116            cache_dir: PathBuf::from(".osm_cache"),
117            connect_timeout: Duration::from_secs(30),
118            read_timeout: Duration::from_secs(180),
119            speed_profile: SpeedProfile::default(),
120            connectivity_policy: ConnectivityPolicy::KeepAll,
121            highway_types: vec![
122                "motorway",
123                "trunk",
124                "primary",
125                "secondary",
126                "tertiary",
127                "residential",
128                "unclassified",
129                "service",
130                "living_street",
131            ],
132        }
133    }
134}
135
136impl NetworkConfig {
137    pub fn new() -> Self {
138        Self::default()
139    }
140
141    pub fn overpass_url(mut self, url: impl Into<String>) -> Self {
142        let url = url.into();
143        self.overpass_url = url.clone();
144        self.overpass_endpoints = vec![url];
145        self
146    }
147
148    pub fn overpass_endpoints(mut self, urls: Vec<String>) -> Self {
149        if let Some(primary) = urls.first().cloned() {
150            self.overpass_url = primary;
151            self.overpass_endpoints = urls;
152        }
153        self
154    }
155
156    pub fn overpass_max_retries(mut self, retries: usize) -> Self {
157        self.overpass_max_retries = retries;
158        self
159    }
160
161    pub fn overpass_retry_backoff(mut self, backoff: Duration) -> Self {
162        self.overpass_retry_backoff = backoff;
163        self
164    }
165
166    pub fn cache_dir(mut self, path: impl Into<PathBuf>) -> Self {
167        self.cache_dir = path.into();
168        self
169    }
170
171    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
172        self.connect_timeout = timeout;
173        self
174    }
175
176    pub fn read_timeout(mut self, timeout: Duration) -> Self {
177        self.read_timeout = timeout;
178        self
179    }
180
181    pub fn speed_profile(mut self, profile: SpeedProfile) -> Self {
182        self.speed_profile = profile;
183        self
184    }
185
186    pub fn connectivity_policy(mut self, policy: ConnectivityPolicy) -> Self {
187        self.connectivity_policy = policy;
188        self
189    }
190
191    pub fn highway_types(mut self, types: Vec<&'static str>) -> Self {
192        self.highway_types = types;
193        self
194    }
195
196    pub(crate) fn highway_regex(&self) -> String {
197        format!("^({})$", self.highway_types.join("|"))
198    }
199}