Skip to content

Commit

Permalink
Reorg the WASM API and what we store where, and make adding a filter
Browse files Browse the repository at this point in the history
work, maybe... can't see results yet
  • Loading branch information
dabreegster committed Dec 20, 2023
1 parent 2dbb67e commit 9622a84
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 148 deletions.
1 change: 0 additions & 1 deletion backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ geojson = { git = "https://github.com/georust/geojson", features = ["geo-types"]
log = "0.4.20"
osm-reader = { git = "https://github.com/a-b-street/osm-reader" }
route-snapper-graph = { git = "https://github.com/dabreegster/route_snapper", branch = "no_osm2streets" }
rstar = { version = "0.11.0" }
serde = "1.0.188"
serde_json = "1.0.105"
serde-wasm-bindgen = "0.6.0"
Expand Down
156 changes: 49 additions & 107 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ extern crate anyhow;
#[macro_use]
extern crate log;

use std::collections::BTreeMap;
use std::fmt;
use std::sync::Once;

use geo::{EuclideanLength, LineString, Point, Polygon};
use geojson::{Feature, GeoJson, Geometry};
use serde::{Deserialize, Serialize};
use geo::{Coord, Polygon};
use geojson::{Feature, GeoJson};
use serde::Deserialize;
use wasm_bindgen::prelude::*;

use self::cells::Cell;
use self::map_model::{Intersection, IntersectionID, MapModel, Road, RoadID};
use self::mercator::Mercator;
use self::neighbourhood::Neighbourhood;
use self::render_cells::RenderCells;
use self::shortcuts::Shortcuts;
use self::tags::Tags;

mod cells;
mod map_model;
mod mercator;
mod neighbourhood;
mod node_map;
Expand All @@ -29,82 +31,37 @@ mod tags;
static START: Once = Once::new();

#[wasm_bindgen]
pub struct MapModel {
roads: Vec<Road>,
intersections: Vec<Intersection>,
// All geometry stored in worldspace, including rtrees
mercator: mercator::Mercator,

// TODO Split stuff
modal_filters: BTreeMap<RoadID, ModalFilter>,
}

impl MapModel {
fn get_r(&self, r: RoadID) -> &Road {
&self.roads[r.0]
}

fn get_i(&self, i: IntersectionID) -> &Intersection {
&self.intersections[i.0]
}
}

#[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)
}
}

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

pub struct Intersection {
id: IntersectionID,
node: osm_reader::NodeID,
point: Point,
roads: Vec<RoadID>,
pub struct LTN {
map: MapModel,
// TODO Stateful, synced with the UI. Weird?
neighbourhood: Option<Neighbourhood>,
}

#[wasm_bindgen]
impl MapModel {
impl LTN {
/// Call with bytes of an osm.pbf or osm.xml string
#[wasm_bindgen(constructor)]
pub fn new(input_bytes: &[u8]) -> Result<MapModel, JsValue> {
pub fn new(input_bytes: &[u8]) -> Result<LTN, JsValue> {
// Panics shouldn't happen, but if they do, console.log them.
console_error_panic_hook::set_once();
START.call_once(|| {
console_log::init_with_level(log::Level::Info).unwrap();
});

scrape::scrape_osm(input_bytes).map_err(err_to_js)
let map = MapModel::new(input_bytes).map_err(err_to_js)?;
Ok(LTN {
map,
neighbourhood: None,
})
}

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

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

let gj = GeoJson::from(features);
Expand All @@ -117,16 +74,16 @@ impl MapModel {
use route_snapper_graph::{Edge, NodeID, RouteSnapperMap};

let mut nodes = Vec::new();
for i in &self.intersections {
nodes.push(self.mercator.to_wgs84(&i.point).into());
for i in &self.map.intersections {
nodes.push(self.map.mercator.to_wgs84(&i.point).into());
}

let mut edges = Vec::new();
for r in &self.roads {
for r in &self.map.roads {
edges.push(Edge {
node1: NodeID(r.src_i.0 as u32),
node2: NodeID(r.dst_i.0 as u32),
geometry: self.mercator.to_wgs84(&r.linestring),
geometry: self.map.mercator.to_wgs84(&r.linestring),
// Isn't serialized, doesn't matter
length_meters: 0.0,
name: r.tags.get("name").cloned(),
Expand All @@ -140,49 +97,38 @@ impl MapModel {

/// Takes boundary GJ polygon, returns GJ with more details
#[wasm_bindgen(js_name = analyzeNeighbourhood)]
pub fn analyze_neighbourhood(&self, input: JsValue) -> Result<String, JsValue> {
pub fn analyze_neighbourhood(&mut self, input: JsValue) -> Result<String, JsValue> {
let boundary_gj: Feature = serde_wasm_bindgen::from_value(input)?;
let mut boundary_geo: Polygon = boundary_gj.try_into().map_err(err_to_js)?;
self.mercator.to_mercator_in_place(&mut boundary_geo);

let neighbourhood = Neighbourhood::new(self, boundary_geo).map_err(err_to_js)?;
Ok(serde_json::to_string(&neighbourhood.to_gj(self)).map_err(err_to_js)?)
}

#[wasm_bindgen(js_name = addModalFilter)]
pub fn add_modal_filter(&self, input: JsValue) -> Result<(), JsValue> {
let pos: LngLat = serde_wasm_bindgen::from_value(input)?;
info!("add to {}, {}", pos.lng, pos.lat);
Ok(())
}
self.map.mercator.to_mercator_in_place(&mut boundary_geo);

fn find_edge(&self, i1: IntersectionID, i2: IntersectionID) -> &Road {
// TODO Store lookup table
for r in &self.get_i(i1).roads {
let road = self.get_r(*r);
if road.src_i == i2 || road.dst_i == i2 {
return road;
}
}
panic!("no road from {i1} to {i2} or vice versa");
self.neighbourhood = Some(Neighbourhood::new(&self.map, boundary_geo).map_err(err_to_js)?);
Ok(
serde_json::to_string(&self.neighbourhood.as_ref().unwrap().to_gj(&self.map))
.map_err(err_to_js)?,
)
}
}

impl Road {
fn length(&self) -> f64 {
self.linestring.euclidean_length()
#[wasm_bindgen(js_name = unsetNeighbourhood)]
pub fn unset_neighbourhood(&mut self) {
self.neighbourhood = None;
}

fn to_gj(&self, mercator: &mercator::Mercator) -> Feature {
let mut f = Feature::from(Geometry::from(&mercator.to_wgs84(&self.linestring)));
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
/// Takes a LngLat, returns the same GJ as analyze_neighbourhood
#[wasm_bindgen(js_name = addModalFilter)]
pub fn add_modal_filter(&mut self, input: JsValue) -> Result<String, JsValue> {
let pos: LngLat = serde_wasm_bindgen::from_value(input)?;
self.map.add_modal_filter(
self.map.mercator.pt_to_mercator(Coord {
x: pos.lng,
y: pos.lat,
}),
&self.neighbourhood.as_ref().unwrap().interior_roads,
);
Ok(
serde_json::to_string(&self.neighbourhood.as_ref().unwrap().to_gj(&self.map))
.map_err(err_to_js)?,
)
}
}

Expand All @@ -192,10 +138,6 @@ struct LngLat {
lat: f64,
}

pub struct ModalFilter {
pub distance: f64,
}

fn err_to_js<E: std::fmt::Display>(err: E) -> JsValue {
JsValue::from_str(&err.to_string())
}
130 changes: 130 additions & 0 deletions backend/src/map_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::collections::{BTreeMap, HashSet};
use std::fmt;

use anyhow::Result;
use geo::{
Closest, ClosestPoint, Coord, EuclideanLength, Line, LineLocatePoint, LineString, Point,
};
use geojson::{Feature, Geometry};
use serde::Serialize;

use crate::{Mercator, Tags};

pub struct MapModel {
pub roads: Vec<Road>,
pub intersections: Vec<Intersection>,
// All geometry stored in worldspace, including rtrees
pub mercator: Mercator,

// TODO Keep edits / state here or not?
pub modal_filters: BTreeMap<RoadID, ModalFilter>,
}

#[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)
}
}

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,
}

pub struct Intersection {
pub id: IntersectionID,
pub node: osm_reader::NodeID,
pub point: Point,
pub roads: Vec<RoadID>,
}

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

pub fn get_r(&self, r: RoadID) -> &Road {
&self.roads[r.0]
}

pub fn get_i(&self, i: IntersectionID) -> &Intersection {
&self.intersections[i.0]
}

pub fn find_edge(&self, i1: IntersectionID, i2: IntersectionID) -> &Road {
// TODO Store lookup table
for r in &self.get_i(i1).roads {
let road = self.get_r(*r);
if road.src_i == i2 || road.dst_i == i2 {
return road;
}
}
panic!("no road from {i1} to {i2} or vice versa");
}

pub fn add_modal_filter(&mut self, click_pt: Coord, candidate_roads: &HashSet<RoadID>) {
// TODO prune with rtree?
let (_, r, dist_along) = candidate_roads
.iter()
.map(|r| {
let road = self.get_r(*r);
let hit_pt = match road.linestring.closest_point(&click_pt.into()) {
Closest::Intersection(pt) => pt,
Closest::SinglePoint(pt) => pt,
Closest::Indeterminate => unreachable!(),
};
let score = Line::new(click_pt, hit_pt.into()).euclidean_length();
let dist_along = road.linestring.line_locate_point(&hit_pt).unwrap();
((score * 100.0) as usize, road.id, dist_along)
})
.max_by_key(|pair| pair.0)
.unwrap();
self.modal_filters.insert(
r,
ModalFilter {
distance: dist_along,
},
);
info!("added a filter to {r} at {dist_along}");
}
}

impl Road {
pub fn length(&self) -> f64 {
self.linestring.euclidean_length()
}

pub fn to_gj(&self, mercator: &Mercator) -> Feature {
let mut f = Feature::from(Geometry::from(&mercator.to_wgs84(&self.linestring)));
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
}
}

pub struct ModalFilter {
pub distance: f64,
}
4 changes: 1 addition & 3 deletions backend/src/scrape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ use anyhow::Result;
use geo::{Coord, Geometry, GeometryCollection, LineString, Point};
use osm_reader::{Element, NodeID, WayID};

use crate::mercator::Mercator;
use crate::tags::Tags;
use crate::{Intersection, IntersectionID, MapModel, Road, RoadID};
use crate::{Intersection, IntersectionID, MapModel, Mercator, Road, RoadID, Tags};

struct Way {
id: WayID,
Expand Down
Loading

0 comments on commit 9622a84

Please sign in to comment.