diff --git a/examples/seattle/config.json b/examples/seattle/config.json index ce653c7..aae6745 100644 --- a/examples/seattle/config.json +++ b/examples/seattle/config.json @@ -7,7 +7,14 @@ "origins_path": "", "destinations_path": "" }, - "cost": "AvoidMainRoads", + "cost": { + "ByLTS": { + "lts1": 1.0, + "lts2": 1.3, + "lts3": 2.0, + "lts4": 3.0 + } + }, "uptake": "Identity", "lts": "BikeOttawa" } diff --git a/lts/src/allowed.rs b/lts/src/allowed.rs new file mode 100644 index 0000000..a67a835 --- /dev/null +++ b/lts/src/allowed.rs @@ -0,0 +1,43 @@ +use crate::Tags; + +pub fn is_cycling_allowed(tags: &Tags, msgs: &mut Vec) -> bool { + if !tags.has("highway") && !tags.has("bicycle") { + msgs.push("Way doesn't have a highway or bicycle tag".into()); + return false; + } + + if tags.is("bicycle", "no") { + msgs.push("Cycling not permitted due to bicycle=no".into()); + return false; + } + + if tags.is("access", "no") { + // TODO There are exceptions for bicycle + msgs.push("Cycling not permitted due to access=no".into()); + return false; + } + + if tags.is_any( + "highway", + vec!["motorway", "motorway_link", "proposed", "construction"], + ) { + msgs.push(format!( + "Cycling not permitted due to highway={}", + tags.get("highway").unwrap() + )); + return false; + } + + if tags.is_any("highway", vec!["footway", "path"]) + && tags.is("footway", "sidewalk") + && !tags.is("bicycle", "yes") + { + msgs.push(format!( + "Cycling not permitted on highway={}, when footway=sidewalk and bicycle=yes is missing", + tags.get("highway").unwrap() + )); + return false; + } + + true +} diff --git a/lts/src/bike_ottawa.rs b/lts/src/bike_ottawa.rs index c7abada..18c350f 100644 --- a/lts/src/bike_ottawa.rs +++ b/lts/src/bike_ottawa.rs @@ -1,4 +1,4 @@ -use crate::{parse, Tags, LTS}; +use crate::{is_cycling_allowed, parse, Tags, LTS}; // The below is adapted from https://raw.githubusercontent.com/BikeOttawa/stressmodel/master/stressmodel.js, MIT licensed // TODO Ask about differences: maxspeed parsing, highway=construction @@ -7,7 +7,7 @@ use crate::{parse, Tags, LTS}; pub fn bike_ottawa(tags: &Tags) -> (LTS, Vec) { let mut msgs = Vec::new(); - if !is_biking_permitted(&tags, &mut msgs) { + if !is_cycling_allowed(&tags, &mut msgs) { return (LTS::NotAllowed, msgs); } @@ -28,48 +28,6 @@ pub fn bike_ottawa(tags: &Tags) -> (LTS, Vec) { (LTS::NotAllowed, msgs) } -fn is_biking_permitted(tags: &Tags, msgs: &mut Vec) -> bool { - if !tags.has("highway") && !tags.has("bicycle") { - msgs.push("Way doesn't have a highway or bicycle tag".into()); - return false; - } - - if tags.is("bicycle", "no") { - msgs.push("Cycling not permitted due to bicycle=no".into()); - return false; - } - - if tags.is("access", "no") { - // TODO There are exceptions for bicycle - msgs.push("Cycling not permitted due to access=no".into()); - return false; - } - - if tags.is_any( - "highway", - vec!["motorway", "motorway_link", "proposed", "construction"], - ) { - msgs.push(format!( - "Cycling not permitted due to highway={}", - tags.get("highway").unwrap() - )); - return false; - } - - if tags.is_any("highway", vec!["footway", "path"]) - && tags.is("footway", "sidewalk") - && !tags.is("bicycle", "yes") - { - msgs.push(format!( - "Cycling not permitted on highway={}, when footway=sidewalk and bicycle=yes is missing", - tags.get("highway").unwrap() - )); - return false; - } - - true -} - fn is_separate_path(tags: &Tags, msgs: &mut Vec) -> bool { if tags.is_any("highway", vec!["cycleway", "path"]) { msgs.push(format!( diff --git a/lts/src/lib.rs b/lts/src/lib.rs index 53cd3b8..03f4a5c 100644 --- a/lts/src/lib.rs +++ b/lts/src/lib.rs @@ -1,3 +1,4 @@ +mod allowed; mod bike_ottawa; mod parse; mod speed_limit_only; @@ -9,6 +10,7 @@ mod wasm; use serde_repr::{Deserialize_repr, Serialize_repr}; +pub use allowed::is_cycling_allowed; pub use bike_ottawa::bike_ottawa; pub use speed_limit_only::speed_limit_only; pub use tags::Tags; diff --git a/lts/src/speed_limit_only.rs b/lts/src/speed_limit_only.rs index 363d12b..6cd0b11 100644 --- a/lts/src/speed_limit_only.rs +++ b/lts/src/speed_limit_only.rs @@ -1,27 +1,19 @@ -use crate::{Tags, LTS}; +use crate::{is_cycling_allowed, parse, Tags, LTS}; pub fn speed_limit_only(tags: &Tags) -> (LTS, Vec) { - let msgs = vec!["Only looking at maxspeed".into()]; - // TODO Handle bicycle=no, on things like highway=footway + let mut msgs = vec!["Only looking at maxspeed".into()]; - // TODO Use parse::get_maxspeed_mph - if let Some(mph) = tags - .get("maxspeed") - .and_then(|x| x.trim_end_matches(" mph").parse::().ok()) - { - if mph <= 20 { - return (LTS::LTS2, msgs); - } - if mph >= 40 { - return (LTS::LTS4, msgs); - } - // Between 20 and 40 - return (LTS::LTS3, msgs); + if !is_cycling_allowed(tags, &mut msgs) { + return (LTS::NotAllowed, msgs); } - /*if tags.is("highway", "residential") { - return LTS::LTS1; - }*/ - - (LTS::NotAllowed, msgs) + let mph = parse::get_maxspeed_mph(tags, &mut msgs); + if mph <= 20 { + return (LTS::LTS2, msgs); + } + if mph >= 40 { + return (LTS::LTS4, msgs); + } + // Between 20 and 40 + (LTS::LTS3, msgs) } diff --git a/od2net/src/config.rs b/od2net/src/config.rs index 9223776..e7fcd3e 100644 --- a/od2net/src/config.rs +++ b/od2net/src/config.rs @@ -58,10 +58,9 @@ pub enum ODPattern { #[derive(Clone, PartialEq, Serialize, Deserialize)] pub enum CostFunction { - /// Just find the shortest distance path + /// Just find the most direct path, minimizing distance. This is equivalent to ByLTS with all + /// weights set to 1. Distance, - /// Heavily penalize main roads - AvoidMainRoads, /// Multiply distance by a factor for each LTS classification ByLTS { lts1: f64, diff --git a/od2net/src/plugins/cost.rs b/od2net/src/plugins/cost.rs index a603f61..c917821 100644 --- a/od2net/src/plugins/cost.rs +++ b/od2net/src/plugins/cost.rs @@ -12,7 +12,6 @@ use lts::LTS; pub fn calculate_batch(cost: &CostFunction, input_batch: Vec<&Edge>) -> Vec> { match cost { CostFunction::Distance => input_batch.into_iter().map(distance).collect(), - CostFunction::AvoidMainRoads => input_batch.into_iter().map(avoid_main_roads).collect(), CostFunction::OsmHighwayType(ref weights) => input_batch .into_iter() .map(|e| osm_highway_type(e, weights)) @@ -31,34 +30,7 @@ pub fn calculate_batch(cost: &CostFunction, input_batch: Vec<&Edge>) -> Vec Option { - // TODO Match the lts.ts definition - if edge.tags.is("bicycle", "no") - || edge.tags.is("highway", "motorway") - || edge.tags.is("highway", "proposed") - { - return None; - } - - Some(edge.length_meters.round() as usize) -} - -fn avoid_main_roads(edge: &Edge) -> Option { - // TODO Match the lts.ts definition - if edge.tags.is("bicycle", "no") - || edge.tags.is("highway", "motorway") - || edge.tags.is("highway", "proposed") - { - return None; - } - - // TODO Reframe this to just penalize by LTS? - let penalty = if edge.tags.is("highway", "residential") || edge.tags.is("highway", "cycleway") { - 1.0 - } else { - 5.0 - }; - - Some((penalty * edge.length_meters).round() as usize) + by_lts(edge, 1.0, 1.0, 1.0, 1.0) } fn osm_highway_type(edge: &Edge, weights: &HashMap) -> Option { diff --git a/viewer/src/CostFunction.svelte b/viewer/src/CostFunction.svelte index fc5a299..fef2b5e 100644 --- a/viewer/src/CostFunction.svelte +++ b/viewer/src/CostFunction.svelte @@ -57,7 +57,6 @@ Cost function: diff --git a/wasm-od2net/src/lib.rs b/wasm-od2net/src/lib.rs index 776fa35..68b92d3 100644 --- a/wasm-od2net/src/lib.rs +++ b/wasm-od2net/src/lib.rs @@ -150,7 +150,10 @@ impl JsNetwork { #[wasm_bindgen(js_name = updateCostFunction)] pub fn update_cost_function(&mut self, input: JsValue) -> Result<(), JsValue> { let cost: CostFunction = serde_wasm_bindgen::from_value(input)?; - info!("Changing cost to {}", serde_json::to_string(&cost).map_err(err_to_js)?); + info!( + "Changing cost to {}", + serde_json::to_string(&cost).map_err(err_to_js)? + ); self.last_cost = cost; self.network.recalculate_cost(&self.last_cost); // Doesn't touch the CH, because this is only meant to be used in the edge cost app, which