diff --git a/lts/src/lib.rs b/lts/src/lib.rs index 03f4a5c..bbec45b 100644 --- a/lts/src/lib.rs +++ b/lts/src/lib.rs @@ -5,6 +5,7 @@ mod speed_limit_only; mod tags; #[cfg(test)] mod tests; +mod walking; #[cfg(target_arch = "wasm32")] mod wasm; @@ -14,6 +15,7 @@ pub use allowed::is_cycling_allowed; pub use bike_ottawa::bike_ottawa; pub use speed_limit_only::speed_limit_only; pub use tags::Tags; +pub use walking::walking; #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize_repr, Deserialize_repr)] #[repr(u8)] diff --git a/lts/src/walking.rs b/lts/src/walking.rs new file mode 100644 index 0000000..a34fb75 --- /dev/null +++ b/lts/src/walking.rs @@ -0,0 +1,105 @@ +use crate::{parse, Tags, LTS}; + +/// Ported from +/// https://github.com/acteng/edge_level_walkability_function/blob/main/walking-lts-prototyping.ipynb +pub fn walking(tags: &Tags) -> (LTS, Vec) { + let mut msgs = Vec::new(); + + let speed_mph = parse::get_maxspeed_mph(tags, &mut msgs); + + if !can_traverse_on_foot(tags) || is_sidewalk(tags) || tags.is("highway", "elevator") { + return (LTS::NotAllowed, msgs); + } + + if is_separate_footpath(tags) { + (LTS::LTS1, msgs) + } else if is_pleasant_road(tags, speed_mph) || tags.is("highway", "steps") { + (LTS::LTS2, msgs) + } else if !tags.is_any("highway", vec!["motorway", "motorway_link"]) + && is_quite_unpleasant_road(tags, speed_mph) + { + (LTS::LTS3, msgs) + } else if tags.is_any( + "highway", + vec!["motorway", "motorway_link", "trunk", "trunk_link"], + ) || speed_mph > 40 + { + (LTS::LTS4, msgs) + } else { + // TODO What cases are these? + (LTS::NotAllowed, msgs) + } +} + +fn can_traverse_on_foot(tags: &Tags) -> bool { + if !tags.has("highway") || tags.is("foot", "no") { + return false; + } + if tags.is("access", "no") && !tags.is_any("foot", vec!["yes", "designated", "permissive"]) { + return false; + } + if tags.is_any("highway", vec!["motorway", "motorway_link", "proposed"]) { + return false; + } + true +} + +fn is_separate_footpath(tags: &Tags) -> bool { + if tags.is_any("highway", vec!["pedestrian", "path", "living_street"]) { + return true; + } + if tags.is("highway", "footway") + && !tags.is_any("footway", vec!["crossing", "link", "traffic_island"]) + { + return true; + } + if tags.is("highway", "cycleway") && tags.is("footway", "designated") { + return true; + } + false +} + +fn is_pleasant_road(tags: &Tags, speed_mph: usize) -> bool { + if tags.is_any( + "highway", + vec![ + "service", + "alley", + "driveway", + "parking_aisle", + "residential", + "bridleway", + "corridor", + "track", + "tertiary", + ], + ) && speed_mph <= 20 + { + return true; + } + if tags.is("highway", "footway") + && tags.is_any("footway", vec!["crossing", "link", "traffic_island"]) + { + return true; + } + false +} + +// TODO Logic here seems wrong +fn is_quite_unpleasant_road(tags: &Tags, speed_mph: usize) -> bool { + let big_highway_type = tags.is_any( + "highway", + vec!["trunk", "trunk_link", "primary", "primary_link"], + ); + if !big_highway_type && speed_mph <= 40 { + return true; + } + if big_highway_type && speed_mph <= 20 { + return true; + } + false +} + +fn is_sidewalk(tags: &Tags) -> bool { + tags.is("highway", "footway") && tags.is("footway", "sidewalk") +} diff --git a/lts/src/wasm.rs b/lts/src/wasm.rs index 010df23..7c5b0ab 100644 --- a/lts/src/wasm.rs +++ b/lts/src/wasm.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use wasm_bindgen::prelude::*; -use crate::{bike_ottawa, speed_limit_only, Tags, LTS}; +use crate::{bike_ottawa, speed_limit_only, walking, Tags, LTS}; #[derive(Deserialize)] struct Input { @@ -29,6 +29,8 @@ pub fn calculate(input: JsValue) -> Result { speed_limit_only::speed_limit_only(&tags) } else if input.method == "bike_ottawa" { bike_ottawa::bike_ottawa(&tags) + } else if input.method == "walking" { + walking::walking(&tags) } else { ( LTS::NotAllowed, diff --git a/od2net/src/config.rs b/od2net/src/config.rs index 3a9c1e0..0f4626f 100644 --- a/od2net/src/config.rs +++ b/od2net/src/config.rs @@ -163,6 +163,7 @@ pub enum Uptake { pub enum LtsMapping { SpeedLimitOnly, BikeOttawa, + Walking, /// Run this command to calculate LTS. STDIN will contain a JSON array of objects, each with /// OSM tags representing one segment. The output must be an equally sized JSON array of /// numbers 0-4, representing the resulting LTS. diff --git a/od2net/src/plugins/lts.rs b/od2net/src/plugins/lts.rs index 1827e94..e97c5c2 100644 --- a/od2net/src/plugins/lts.rs +++ b/od2net/src/plugins/lts.rs @@ -17,6 +17,10 @@ pub fn calculate_lts_batch(lts: &LtsMapping, tags_batch: Vec<&Tags>) -> Vec .into_iter() .map(|tags| lts::bike_ottawa(tags).0) .collect(), + LtsMapping::Walking => tags_batch + .into_iter() + .map(|tags| lts::walking(tags).0) + .collect(), LtsMapping::ExternalCommand(command) => external_command(command, tags_batch).unwrap(), } } diff --git a/od2net/src/plugins/uptake.rs b/od2net/src/plugins/uptake.rs index 451d812..fabbf23 100644 --- a/od2net/src/plugins/uptake.rs +++ b/od2net/src/plugins/uptake.rs @@ -196,13 +196,12 @@ mod tests { // #' uptake_pct_govtarget_school2(3.51, 1.11) // #' [1] 0.05584607 - // #' # pcycle = exp(1.953)/(1 + exp(1.953)) = .8758, or 87.58%. - // #' uptake_pct_godutch_school2(3.51, 1.11) + // #' # pcycle = exp(1.953)/(1 + exp(1.953)) = .8758, or 87.58%. + // #' uptake_pct_godutch_school2(3.51, 1.11) // #' [1] 0.875 #[test] fn test_school() { assert!((pct_gov_target_school(3.51 * 1000.0, 1.11) - 0.05584607).abs() < 1e-4); assert!((pct_go_dutch_school(3.51 * 1000.0, 1.11) - 0.8758).abs() < 1e-4); } - }