Skip to content

Commit

Permalink
Bring in some of the code for calculating cells
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Dec 20, 2023
1 parent c191181 commit 461af23
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 63 deletions.
203 changes: 203 additions & 0 deletions backend/src/cells.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use std::collections::{BTreeMap, BTreeSet};

use crate::{IntersectionID, MapModel, Neighbourhood, Road, RoadID};

/// A partitioning of the interior of a neighbourhood based on driving connectivity
pub struct Cell {
/// Most roads are fully in one cell. Roads with modal filters on them are sometimes split
/// between two cells, and the DistanceInterval indicates the split.
pub roads: BTreeMap<RoadID, DistanceInterval>,
/// Intersections where this cell touches the boundary of the neighbourhood.
pub borders: BTreeSet<IntersectionID>,
}

impl Cell {
/// A cell is disconnected if it's not connected to a perimeter road.
pub fn is_disconnected(&self) -> bool {
self.borders.is_empty()
}

/// Find all of the disconnected of reachable areas, bounded by border intersections. This is
/// with respect to driving.
pub fn find_all(
map: &MapModel,
neighbourhood: &Neighbourhood,
modal_filters: &BTreeMap<RoadID, ModalFilter>,
) -> Vec<Cell> {
let mut cells = Vec::new();
let mut visited = BTreeSet::new();

for start in &neighbourhood.interior_roads {
if visited.contains(start) || modal_filters.contains_key(start) {
continue;
}
let start = *start;
let road = map.get_r(start);
// Just skip entirely; they're invisible for the purpose of dividing into cells
if !is_driveable(road) {
continue;
}
// There are non-private roads connected only to private roads, like
// https://www.openstreetmap.org/way/725759378 and
// https://www.openstreetmap.org/way/27890699. Also skip these, to avoid creating a
// disconnected cell.
let connected_to_public_road = [road.src_i, road.dst_i]
.into_iter()
.flat_map(|i| &map.get_i(i).roads)
.any(|r| *r != start && !is_private(map.get_r(*r)));
if !connected_to_public_road {
continue;
}

let cell = floodfill(map, start, neighbourhood, modal_filters);
visited.extend(cell.roads.keys().cloned());

cells.push(cell);
}

// Filtered roads right along the perimeter have a tiny cell
for (r, filter) in modal_filters {
let road = map.get_r(*r);
if neighbourhood.border_intersections.contains(&road.src_i) {
let mut cell = Cell {
roads: BTreeMap::new(),
borders: BTreeSet::from([road.src_i]),
};
cell.roads.insert(
road.id,
DistanceInterval {
start: 0.0,
end: filter.distance,
},
);
cells.push(cell);
}
if neighbourhood.border_intersections.contains(&road.dst_i) {
let mut cell = Cell {
roads: BTreeMap::new(),
borders: BTreeSet::from([road.dst_i]),
};
cell.roads.insert(
road.id,
DistanceInterval {
start: filter.distance,
end: road.length(),
},
);
cells.push(cell);
}
}

cells
}
}

/// An interval along a road's length, with start < end.
pub struct DistanceInterval {
pub start: f64,
pub end: f64,
}

pub struct ModalFilter {
pub distance: f64,
}

fn floodfill(
map: &MapModel,
start: RoadID,
neighbourhood: &Neighbourhood,
modal_filters: &BTreeMap<RoadID, ModalFilter>,
) -> Cell {
let mut visited_roads: BTreeMap<RoadID, DistanceInterval> = BTreeMap::new();
let mut cell_borders = BTreeSet::new();
// We don't need a priority queue
let mut queue = vec![start];

// The caller should handle this case
assert!(!modal_filters.contains_key(&start));
assert!(is_driveable(map.get_r(start)));

while !queue.is_empty() {
let current = map.get_r(queue.pop().unwrap());
if visited_roads.contains_key(&current.id) {
continue;
}
visited_roads.insert(
current.id,
DistanceInterval {
start: 0.0,
end: current.length(),
},
);

for i in [current.src_i, current.dst_i] {
// It's possible for one border intersection to have two roads in the interior of the
// neighbourhood. Don't consider a turn between those roads through this intersection as
// counting as connectivity -- we're right at the boundary road, so it's like leaving
// and re-entering the neighbourhood.
if neighbourhood.border_intersections.contains(&i) {
cell_borders.insert(i);
continue;
}

for next in &map.get_i(i).roads {
let next_road = map.get_r(*next);
/*if let Some(ref filter) = map.get_i(i).modal_filter {
if !filter.allows_turn(current.id, *next) {
continue;
}
}*/
if let Some(ref filter) = modal_filters.get(next) {
// Which ends of the filtered road have we reached?
let mut visited_start = next_road.src_i == i;
let mut visited_end = next_road.dst_i == i;
// We may have visited previously from the other side.
if let Some(interval) = visited_roads.get(next) {
if interval.start == 0.0 {
visited_start = true;
}
if interval.end == next_road.length() {
visited_end = true;
}
}
visited_roads.insert(
*next,
DistanceInterval {
start: if visited_start { 0.0 } else { filter.distance },
end: if visited_end {
next_road.length()
} else {
filter.distance
},
},
);
continue;
}

if !is_driveable(next_road) {
continue;
}
// TODO This happens near weird geometry. This is OK, but should root-cause it.
if !neighbourhood.interior_roads.contains(next) {
error!("A cell leaked out to {next} from {i}");
continue;
}

queue.push(*next);
}
}
}

Cell {
roads: visited_roads,
borders: cell_borders,
}
}

// TODO
fn is_driveable(road: &Road) -> bool {
true
}
fn is_private(road: &Road) -> bool {
false
}
29 changes: 24 additions & 5 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ extern crate log;
use std::fmt;
use std::sync::Once;

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

use self::cells::Cell;
use self::neighbourhood::Neighbourhood;
use self::shortcuts::Shortcuts;

mod cells;
mod mercator;
mod neighbourhood;
mod scrape;
mod shortcuts;
mod tags;

static START: Once = Once::new();
Expand All @@ -25,6 +31,16 @@ pub struct MapModel {
mercator: mercator::Mercator,
}

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)]
Expand Down Expand Up @@ -121,15 +137,14 @@ impl MapModel {
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::Neighbourhood::new(self, boundary_geo).map_err(err_to_js)?;
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)?)
}

fn find_edge(&self, i1: IntersectionID, i2: IntersectionID) -> &Road {
// TODO Store lookup table
for r in &self.intersections[i1.0].roads {
let road = &self.roads[r.0];
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;
}
Expand All @@ -139,6 +154,10 @@ impl MapModel {
}

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

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);
Expand Down
Loading

0 comments on commit 461af23

Please sign in to comment.