Begin an LTN experiment. Starting from a single point, auto-discover the neighborhood and perimeter roads.

This commit is contained in:
Dustin Carlino 2021-09-23 12:26:37 -07:00
parent a5ed6e79fb
commit 37c1145df4
3 changed files with 161 additions and 0 deletions

View File

@ -0,0 +1,55 @@
use std::collections::BTreeSet;
use map_model::osm::RoadRank;
use map_model::{Map, PathConstraints, RoadID};
use crate::debug::ltn::Neighborhood;
impl Neighborhood {
// TODO Doesn't find the full perimeter. But do we really need that?
pub fn from_road(map: &Map, start: RoadID) -> Neighborhood {
// Do a simple floodfill from this road, stopping anytime we find a major road
let mut interior = BTreeSet::new();
let mut perimeter = BTreeSet::new();
let mut borders = BTreeSet::new();
// We don't need a priority queue
let mut visited = BTreeSet::new();
let mut queue = vec![start];
interior.insert(start);
while !queue.is_empty() {
let current = map.get_r(queue.pop().unwrap());
if visited.contains(&current.id) {
continue;
}
visited.insert(current.id);
for i in [current.src_i, current.dst_i] {
let (minor, major): (Vec<&RoadID>, Vec<&RoadID>) =
map.get_i(i).roads.iter().partition(|r| {
let road = map.get_r(**r);
road.get_rank() == RoadRank::Local
&& road
.lanes
.iter()
.any(|l| PathConstraints::Car.can_use(l, map))
});
if major.is_empty() {
for r in minor {
interior.insert(*r);
queue.push(*r);
}
} else {
borders.insert(i);
perimeter.extend(major);
}
}
}
Neighborhood {
interior,
perimeter,
borders,
}
}
}

101
game/src/debug/ltn/mod.rs Normal file
View File

@ -0,0 +1,101 @@
use std::collections::BTreeSet;
use map_gui::tools::ColorDiscrete;
use map_gui::ID;
use map_model::{IntersectionID, RoadID};
use widgetry::{
Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Line, Panel, SimpleState, State,
TextExt, VerticalAlignment, Widget,
};
use crate::app::{App, Transition};
mod algorithms;
pub struct Viewer {
neighborhood: Neighborhood,
draw: Drawable,
}
struct Neighborhood {
interior: BTreeSet<RoadID>,
perimeter: BTreeSet<RoadID>,
borders: BTreeSet<IntersectionID>,
}
impl Viewer {
pub fn start_from_road(ctx: &mut EventCtx, app: &App, start: RoadID) -> Box<dyn State<App>> {
let neighborhood = Neighborhood::from_road(&app.primary.map, start);
let (draw, legend) = neighborhood.render(ctx, app);
let panel = Panel::new_builder(Widget::col(vec![
Widget::row(vec![
Line("LTN tool").small_heading().into_widget(ctx),
ctx.style().btn_close_widget(ctx),
]),
legend,
"Click a road to re-center".text_widget(ctx),
]))
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
.build(ctx);
<dyn SimpleState<_>>::new_state(panel, Box::new(Viewer { neighborhood, draw }))
}
}
impl SimpleState<App> for Viewer {
fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, _: &Panel) -> Transition {
match x {
"close" => Transition::Pop,
_ => unreachable!(),
}
}
fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) {
app.primary.current_selection = match app.mouseover_unzoomed_roads_and_intersections(ctx) {
x @ Some(ID::Road(_)) => x,
Some(ID::Lane(l)) => Some(ID::Road(l.road)),
_ => None,
};
}
fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
ctx.canvas_movement();
if let Some(ID::Road(r)) = app.primary.current_selection {
if ctx.normal_left_click() {
return Transition::Replace(Viewer::start_from_road(ctx, app, r));
}
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &App) {
g.redraw(&self.draw);
}
}
impl Neighborhood {
// Also a legend
fn render(&self, ctx: &mut EventCtx, app: &App) -> (Drawable, Widget) {
let interior = Color::BLUE;
let perimeter = Color::RED;
let border = Color::CYAN;
let mut colorer = ColorDiscrete::new(
app,
vec![
("interior", interior),
("perimeter", perimeter),
("border", border),
],
);
for r in &self.interior {
colorer.add_r(*r, "interior");
}
for r in &self.perimeter {
colorer.add_r(*r, "perimeter");
}
for i in &self.borders {
colorer.add_i(*i, "border");
}
let (unzoomed, _, legend) = colorer.build(ctx);
(unzoomed, legend)
}
}

View File

@ -29,6 +29,7 @@ pub use self::routes::PathCostDebugger;
mod blocked_by;
mod floodfill;
mod ltn;
mod objects;
pub mod path_counter;
mod polygons;
@ -610,6 +611,7 @@ impl ContextualActions for Actions {
if cfg!(not(target_arch = "wasm32")) {
actions.push((Key::M, "merge short segment".to_string()));
}
actions.push((Key::L, "LTN mode".to_string()));
}
ID::Intersection(i) => {
actions.push((Key::H, "hide this".to_string()));
@ -811,6 +813,9 @@ impl ContextualActions for Actions {
abstio::write_json("merge_osm_ways.json".to_string(), &ways);
Transition::Push(reimport_map(ctx, app, Some(orig_ways)))
}
(ID::Lane(l), "LTN mode") => {
Transition::Push(ltn::Viewer::start_from_road(ctx, app, l.road))
}
(ID::Area(a), "debug area geometry") => {
let pts = &app.primary.map.get_a(a).polygon.points();
let center = if pts[0] == *pts.last().unwrap() {