Skip to content

Commit

Permalink
Organize WASM code
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Apr 28, 2024
1 parent 1e29f41 commit 49a4439
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 158 deletions.
163 changes: 163 additions & 0 deletions backend/src/graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use std::fmt;

use anyhow::Result;
use geo::{LineString, Point, Polygon};
use geojson::{Feature, GeoJson, Geometry};
use rstar::{primitives::GeomWithData, RTree};
use serde::Serialize;
use utils::{Mercator, Tags};

pub struct Graph {
pub roads: Vec<Road>,
pub intersections: Vec<Intersection>,
// All geometry stored in worldspace, including rtrees
pub mercator: Mercator,
pub closest_intersection: RTree<IntersectionLocation>,
pub boundary_polygon: Polygon,
}

pub type IntersectionLocation = GeomWithData<[f64; 2], IntersectionID>;

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct RoadID(pub usize);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
pub struct IntersectionID(pub usize);

impl fmt::Display for RoadID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Road #{}", self.0)
}
}

impl fmt::Display for IntersectionID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Intersection #{}", self.0)
}
}

#[derive(Clone, Copy, Debug)]
pub enum Direction {
Forwards,
Backwards,
Both,
None,
}

#[derive(Clone, Copy)]
pub enum Mode {
Car,
Bicycle,
Foot,
}

pub struct Road {
pub id: RoadID,
pub src_i: IntersectionID,
pub dst_i: IntersectionID,
pub way: osm_reader::WayID,
pub node1: osm_reader::NodeID,
pub node2: osm_reader::NodeID,
pub linestring: LineString,
pub tags: Tags,

// A simplified view of who can access a road. All might be None (buses, trains ignored)
// TODO enum map?
pub access_car: Direction,
pub access_bicycle: Direction,
pub access_foot: Direction,
}

pub struct Intersection {
pub id: IntersectionID,
#[allow(dead_code)]
pub node: osm_reader::NodeID,
pub point: Point,
pub roads: Vec<RoadID>,
}

impl Graph {
/// Call with bytes of an osm.pbf or osm.xml string
pub fn new(input_bytes: &[u8]) -> Result<Graph> {
// TODO make a method there
crate::scrape::scrape_osm(input_bytes)
}

/// Returns a GeoJSON string. Just shows the full network
pub fn render(&self) -> Result<String> {
let mut features = Vec::new();

for r in &self.roads {
features.push(r.to_gj(&self.mercator));
}

let gj = GeoJson::from(features);
let out = serde_json::to_string(&gj)?;
Ok(out)
}

/// Return a polygon covering the world, minus a hole for the boundary, in WGS84
pub fn get_inverted_boundary(&self) -> Result<String> {
let (boundary, _) = self.mercator.to_wgs84(&self.boundary_polygon).into_inner();
let polygon = Polygon::new(
LineString::from(vec![
(180.0, 90.0),
(-180.0, 90.0),
(-180.0, -90.0),
(180.0, -90.0),
(180.0, 90.0),
]),
vec![boundary],
);
let f = Feature::from(Geometry::from(&polygon));
let out = serde_json::to_string(&f)?;
Ok(out)
}

pub fn roads_per_intersection(
&self,
i: IntersectionID,
mode: Mode,
) -> impl Iterator<Item = &Road> {
self.intersections[i.0]
.roads
.iter()
.map(|r| &self.roads[r.0])
.filter(move |r| r.allows_forwards(mode) || r.allows_backwards(mode))
}
}

impl Road {
pub fn allows_forwards(&self, mode: Mode) -> bool {
let dir = match mode {
Mode::Car => self.access_car,
Mode::Bicycle => self.access_bicycle,
Mode::Foot => self.access_foot,
};
matches!(dir, Direction::Forwards | Direction::Both)
}

pub fn allows_backwards(&self, mode: Mode) -> bool {
let dir = match mode {
Mode::Car => self.access_car,
Mode::Bicycle => self.access_bicycle,
Mode::Foot => self.access_foot,
};
matches!(dir, Direction::Backwards | Direction::Both)
}

pub fn to_gj(&self, mercator: &Mercator) -> Feature {
let mut f = Feature::from(Geometry::from(&mercator.to_wgs84(&self.linestring)));
// TODO Rethink most of this -- it's debug info
f.set_property("id", self.id.0);
f.set_property("way", self.way.to_string());
f.set_property("node1", self.node1.to_string());
f.set_property("node2", self.node2.to_string());
for (k, v) in &self.tags.0 {
f.set_property(k, v.to_string());
}
f.set_property("access_car", format!("{:?}", self.access_car));
f.set_property("access_bicycle", format!("{:?}", self.access_bicycle));
f.set_property("access_foot", format!("{:?}", self.access_foot));
f
}
}
19 changes: 9 additions & 10 deletions backend/src/isochrone.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};

use crate::{MapModel, RoadID};
use anyhow::Result;
use geo::{Coord, EuclideanLength};

use crate::Mode;
use crate::graph::{Graph, Mode, RoadID};

pub fn calculate(map: &MapModel, req: Coord, mode: Mode) -> Result<String> {
pub fn calculate(graph: &Graph, req: Coord, mode: Mode) -> Result<String> {
// 1km in cm
// TODO Use a real cost type
let limit = 1000 * 100;
let cost_per_road = get_costs(map, req, mode, limit);
let cost_per_road = get_costs(graph, req, mode, limit);

// Show cost per road
let mut features = Vec::new();
for (r, cost) in cost_per_road {
let mut f = geojson::Feature::from(geojson::Geometry::from(
&map.mercator.to_wgs84(&map.roads[r.0].linestring),
&graph.mercator.to_wgs84(&graph.roads[r.0].linestring),
));
f.set_property("cost_meters", (cost as f64) / 100.0);
features.push(f);
Expand All @@ -27,9 +26,9 @@ pub fn calculate(map: &MapModel, req: Coord, mode: Mode) -> Result<String> {
Ok(x)
}

fn get_costs(map: &MapModel, req: Coord, mode: Mode, limit: usize) -> HashMap<RoadID, usize> {
fn get_costs(graph: &Graph, req: Coord, mode: Mode, limit: usize) -> HashMap<RoadID, usize> {
// TODO This needs to be per mode
let start = map
let start = graph
.closest_intersection
.nearest_neighbor(&[req.x, req.y])
.unwrap()
Expand All @@ -38,7 +37,7 @@ fn get_costs(map: &MapModel, req: Coord, mode: Mode, limit: usize) -> HashMap<Ro
let mut queue: BinaryHeap<PriorityQueueItem<usize, RoadID>> = BinaryHeap::new();
// TODO Match closest road. For now, start with all roads for the closest intersection
// TODO Think through directions for this initial case. Going by road is strange.
for road in map.roads_per_intersection(start, mode) {
for road in graph.roads_per_intersection(start, mode) {
queue.push(PriorityQueueItem::new(0, road.id));
}

Expand All @@ -52,7 +51,7 @@ fn get_costs(map: &MapModel, req: Coord, mode: Mode, limit: usize) -> HashMap<Ro
}
cost_per_road.insert(current.value, current.cost);

let current_road = &map.roads[current.value.0];
let current_road = &graph.roads[current.value.0];
// TODO Think through how this search should work with directions. This is assuming
// incorrectly we're starting from src_i.
let mut endpoints = Vec::new();
Expand All @@ -64,7 +63,7 @@ fn get_costs(map: &MapModel, req: Coord, mode: Mode, limit: usize) -> HashMap<Ro
}

for i in endpoints {
for road in map.roads_per_intersection(i, mode) {
for road in graph.roads_per_intersection(i, mode) {
// TODO Different cost per mode
let cost = (100.0 * road.linestring.euclidean_length()).round() as usize;
queue.push(PriorityQueueItem::new(current.cost + cost, road.id));
Expand Down
Loading

0 comments on commit 49a4439

Please sign in to comment.