From d705d7a9485165f7505bd41ec4dcb26d26c98f41 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 20 Dec 2023 16:09:36 +0900 Subject: [PATCH] Switch to fast_paths for shortcuts. 9s to 50ms, as usual :) --- backend/Cargo.lock | 46 ++++++++++--------------- backend/Cargo.toml | 2 +- backend/src/lib.rs | 4 ++- backend/src/node_map.rs | 72 ++++++++++++++++++++++++++++++++++++++++ backend/src/shortcuts.rs | 35 +++++++++++-------- 5 files changed, 114 insertions(+), 45 deletions(-) create mode 100644 backend/src/node_map.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 60e53ca..ac569d3 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -74,11 +74,11 @@ dependencies = [ "console_error_panic_hook", "console_log", "contour", + "fast_paths", "geo", "geojson", "log", "osm-reader", - "petgraph", "route-snapper-graph", "rstar", "serde", @@ -223,12 +223,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "errno" version = "0.3.8" @@ -239,18 +233,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fast_paths" +version = "0.3.0-SNAPSHOT" +source = "git+https://github.com/easbar/fast_paths#4e663e5044afa1cd465bb6f571c7b0035a927f3d" +dependencies = [ + "log", + "priority-queue", + "serde", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "flate2" version = "1.0.28" @@ -374,16 +372,6 @@ dependencies = [ "hashbrown 0.12.3", ] -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", -] - [[package]] name = "itertools" version = "0.11.0" @@ -522,13 +510,13 @@ dependencies = [ ] [[package]] -name = "petgraph" -version = "0.6.4" +name = "priority-queue" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61" dependencies = [ - "fixedbitset", - "indexmap 2.1.0", + "autocfg", + "indexmap", ] [[package]] @@ -573,7 +561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77d6fbd6697c9e531873e81cec565a85e226b99a0f10e1acc079be057fe2fcba" dependencies = [ "anyhow", - "indexmap 1.9.3", + "indexmap", "log", "protobuf", "protobuf-support", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 5177588..c3690dc 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib", "rlib"] anyhow = "1.0.75" console_error_panic_hook = "0.1.6" console_log = "1.0.0" +fast_paths = { git = "https://github.com/easbar/fast_paths" } geo = "0.27.0" geojson = { git = "https://github.com/georust/geojson", features = ["geo-types"] } log = "0.4.20" @@ -22,5 +23,4 @@ serde-wasm-bindgen = "0.6.0" wasm-bindgen = "0.2.87" web-sys = { version = "0.3.64", features = ["console"] } bincode = "1.3.3" -petgraph = "0.6.4" contour = "0.12.0" diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 668b469..9e23893 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -8,6 +8,7 @@ use std::sync::Once; use geo::{EuclideanLength, LineString, Point, Polygon}; use geojson::{Feature, GeoJson, Geometry}; +use serde::Serialize; use wasm_bindgen::prelude::*; use self::cells::Cell; @@ -18,6 +19,7 @@ use self::shortcuts::Shortcuts; mod cells; mod mercator; mod neighbourhood; +mod node_map; mod render_cells; mod scrape; mod shortcuts; @@ -45,7 +47,7 @@ impl MapModel { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct RoadID(pub usize); -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] pub struct IntersectionID(pub usize); impl fmt::Display for RoadID { diff --git a/backend/src/node_map.rs b/backend/src/node_map.rs new file mode 100644 index 0000000..c662fb2 --- /dev/null +++ b/backend/src/node_map.rs @@ -0,0 +1,72 @@ +//! Some helpers for working with fast_paths, adapted from A/B Street + +use std::collections::BTreeMap; +use std::fmt::Debug; + +use fast_paths::NodeId; +use serde::{Deserialize, Deserializer, Serialize}; + +/// A bidirectional mapping between fast_paths NodeId and some custom ID type. +#[derive(Clone, Serialize)] +pub struct NodeMap { + // These two fields are redundant and large, so don't serialize the bigger one, to cut down + // file size. + #[serde(skip_serializing)] + node_to_id: BTreeMap, + id_to_node: Vec, +} + +impl NodeMap { + pub fn new() -> NodeMap { + NodeMap { + node_to_id: BTreeMap::new(), + id_to_node: Vec::new(), + } + } + + pub fn get_or_insert(&mut self, node: T) -> NodeId { + if let Some(id) = self.node_to_id.get(&node) { + return *id; + } + let id = self.id_to_node.len(); + self.node_to_id.insert(node, id); + self.id_to_node.push(node); + id + } + + pub fn get(&self, node: T) -> Option { + self.node_to_id.get(&node).cloned() + } + + pub fn translate_id(&self, id: usize) -> T { + self.id_to_node[id] + } +} + +// A serialized NodeMap has this form in JSON. Use this to deserialize. +#[derive(Deserialize)] +struct InnerNodeMap { + #[allow(dead_code)] + id_to_node: Vec, +} + +#[allow(dead_code)] +pub fn deserialize_nodemap< + 'de, + D: Deserializer<'de>, + T: Deserialize<'de> + Copy + Ord + Debug + Serialize, +>( + d: D, +) -> Result, D::Error> { + let inner = >::deserialize(d)?; + let id_to_node = inner.id_to_node; + let mut node_to_id = BTreeMap::new(); + for (id, node) in id_to_node.iter().enumerate() { + node_to_id.insert(*node, id); + } + + Ok(NodeMap { + node_to_id, + id_to_node, + }) +} diff --git a/backend/src/shortcuts.rs b/backend/src/shortcuts.rs index dcab7d2..213240e 100644 --- a/backend/src/shortcuts.rs +++ b/backend/src/shortcuts.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; -use petgraph::graphmap::DiGraphMap; +use fast_paths::InputGraph; +use crate::node_map::NodeMap; use crate::{MapModel, Neighbourhood, RoadID}; pub struct Shortcuts { @@ -10,27 +11,33 @@ pub struct Shortcuts { impl Shortcuts { pub fn new(map: &MapModel, neighbourhood: &Neighbourhood) -> Self { - let mut graph = DiGraphMap::new(); + let mut input_graph = InputGraph::new(); + let mut node_map = NodeMap::new(); + for r in &neighbourhood.interior_roads { let road = map.get_r(*r); - graph.add_edge(road.src_i, road.dst_i, (road.id, road.length())); + let i1 = node_map.get_or_insert(road.src_i); + let i2 = node_map.get_or_insert(road.dst_i); + let cost = (road.length() * 100.0) as usize; // TODO Look at one-way for driving - graph.add_edge(road.dst_i, road.src_i, (road.id, road.length())); + input_graph.add_edge(i1, i2, cost); + input_graph.add_edge(i2, i1, cost); } + input_graph.freeze(); + let ch = fast_paths::prepare(&input_graph); + let mut path_calc = fast_paths::create_calculator(&ch); let mut count_per_road = HashMap::new(); for start in &neighbourhood.border_intersections { for end in &neighbourhood.border_intersections { - if let Some((_, path)) = petgraph::algo::astar( - &graph, - *start, - |i| i == *end, - |(_, _, (_, dist))| *dist, - |_| 0.0, - ) { - for pair in path.windows(2) { - let (r, _) = *graph.edge_weight(pair[0], pair[1]).unwrap(); - *count_per_road.entry(r).or_insert(0) += 1; + if let (Some(i1), Some(i2)) = (node_map.get(*start), node_map.get(*end)) { + if let Some(path) = path_calc.calc_path(&ch, i1, i2) { + for pair in path.get_nodes().windows(2) { + let i1 = node_map.translate_id(pair[0]); + let i2 = node_map.translate_id(pair[1]); + let road = map.find_edge(i1, i2); + *count_per_road.entry(road.id).or_insert(0) += 1; + } } } }