Rank each street inside a neighborhood as quiet/busy, based on the number of rat-runs through it.

Show this through a heatmap and tooltips
This commit is contained in:
Dustin Carlino 2021-12-19 12:06:42 +00:00
parent 5542632d61
commit 83bf6953d1
3 changed files with 122 additions and 10 deletions

View File

@ -1,9 +1,10 @@
use geom::ArrowCap; use geom::ArrowCap;
use map_model::NORMAL_LANE_THICKNESS; use map_gui::tools::ColorNetwork;
use widgetry::mapspace::ToggleZoomed; use map_model::{IntersectionID, RoadID, NORMAL_LANE_THICKNESS};
use widgetry::mapspace::{ObjectID, ToggleZoomed, World};
use widgetry::{ use widgetry::{
Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, TextExt, Color, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State,
VerticalAlignment, Widget, Text, TextExt, Toggle, VerticalAlignment, Widget,
}; };
use super::rat_runs::{find_rat_runs, RatRuns}; use super::rat_runs::{find_rat_runs, RatRuns};
@ -16,6 +17,8 @@ pub struct BrowseRatRuns {
current_idx: usize, current_idx: usize,
draw_path: ToggleZoomed, draw_path: ToggleZoomed,
draw_heatmap: ToggleZoomed,
world: World<Obj>,
neighborhood: Neighborhood, neighborhood: Neighborhood,
} }
@ -33,12 +36,23 @@ impl BrowseRatRuns {
timer, timer,
) )
}); });
let mut colorer = ColorNetwork::no_fading(app);
colorer.ranked_roads(rat_runs.count_per_road.clone(), &app.cs.good_to_bad_red);
// TODO These two will be on different scales, which'll look really weird!
colorer.ranked_intersections(
rat_runs.count_per_intersection.clone(),
&app.cs.good_to_bad_red,
);
let world = make_world(ctx, app, &neighborhood, &rat_runs);
let mut state = BrowseRatRuns { let mut state = BrowseRatRuns {
panel: Panel::empty(ctx), panel: Panel::empty(ctx),
rat_runs, rat_runs,
current_idx: 0, current_idx: 0,
draw_path: ToggleZoomed::empty(ctx), draw_path: ToggleZoomed::empty(ctx),
draw_heatmap: colorer.build(ctx),
neighborhood, neighborhood,
world,
}; };
state.recalculate(ctx, app); state.recalculate(ctx, app);
Box::new(state) Box::new(state)
@ -91,6 +105,16 @@ impl BrowseRatRuns {
.hotkey(Key::RightArrow) .hotkey(Key::RightArrow)
.build_widget(ctx, "next rat run"), .build_widget(ctx, "next rat run"),
]), ]),
// TODO This should disable the individual path controls, or maybe even be a different
// state entirely...
Toggle::checkbox(
ctx,
"show heatmap of all rat-runs",
Key::R,
self.panel
.maybe_is_checked("show heatmap of all rat-runs")
.unwrap_or(true),
),
])) ]))
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top) .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
.build(ctx); .build(ctx);
@ -140,28 +164,87 @@ impl State<App> for BrowseRatRuns {
} }
"previous rat run" => { "previous rat run" => {
self.current_idx -= 1; self.current_idx -= 1;
self.panel
.set_checked("show heatmap of all rat-runs", false);
self.recalculate(ctx, app); self.recalculate(ctx, app);
} }
"next rat run" => { "next rat run" => {
self.current_idx += 1; self.current_idx += 1;
self.panel
.set_checked("show heatmap of all rat-runs", false);
self.recalculate(ctx, app); self.recalculate(ctx, app);
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
// Just trigger tooltips; no other interactions possible
let _ = self.world.event(ctx);
Transition::Keep Transition::Keep
} }
fn draw(&self, g: &mut GfxCtx, app: &App) { fn draw(&self, g: &mut GfxCtx, app: &App) {
self.panel.draw(g); self.panel.draw(g);
if self.panel.is_checked("show heatmap of all rat-runs") {
self.draw_heatmap.draw(g);
self.world.draw(g);
} else {
self.draw_path.draw(g);
}
g.redraw(&self.neighborhood.fade_irrelevant); g.redraw(&self.neighborhood.fade_irrelevant);
self.neighborhood.draw_filters.draw(g); self.neighborhood.draw_filters.draw(g);
if g.canvas.is_unzoomed() { if g.canvas.is_unzoomed() {
self.neighborhood.labels.draw(g, app); self.neighborhood.labels.draw(g, app);
} }
self.draw_path.draw(g);
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Obj {
InteriorRoad(RoadID),
InteriorIntersection(IntersectionID),
}
impl ObjectID for Obj {}
fn make_world(
ctx: &mut EventCtx,
app: &App,
neighborhood: &Neighborhood,
rat_runs: &RatRuns,
) -> World<Obj> {
let map = &app.primary.map;
let mut world = World::bounded(map.get_bounds());
for r in &neighborhood.orig_perimeter.interior {
world
.add(Obj::InteriorRoad(*r))
.hitbox(map.get_r(*r).get_thick_polygon())
.drawn_in_master_batch()
// TODO Not sure if tooltip() without this should imply it?
.draw_hovered(GeomBatch::new())
.tooltip(Text::from(format!(
"{} rat-runs cross this street",
rat_runs.count_per_road.get(*r)
)))
.build(ctx);
}
for i in &neighborhood.interior_intersections {
world
.add(Obj::InteriorIntersection(*i))
.hitbox(map.get_i(*i).polygon.clone())
.drawn_in_master_batch()
.draw_hovered(GeomBatch::new())
.tooltip(Text::from(format!(
"{} rat-runs cross this intersection",
rat_runs.count_per_intersection.get(*i)
)))
.build(ctx);
}
world.initialize_hover(ctx);
world
}

View File

@ -1,15 +1,17 @@
use std::collections::HashSet; use std::collections::HashSet;
use abstutil::Timer; use abstutil::{Counter, Timer};
use map_model::{ use map_model::{
DirectedRoadID, IntersectionID, LaneID, Map, Path, PathConstraints, PathRequest, PathStep, DirectedRoadID, IntersectionID, LaneID, Map, Path, PathConstraints, PathRequest, PathStep,
Position, Position, RoadID,
}; };
use super::{ModalFilters, Neighborhood}; use super::{ModalFilters, Neighborhood};
pub struct RatRuns { pub struct RatRuns {
pub paths: Vec<Path>, pub paths: Vec<Path>,
pub count_per_road: Counter<RoadID>,
pub count_per_intersection: Counter<IntersectionID>,
} }
pub fn find_rat_runs( pub fn find_rat_runs(
@ -65,9 +67,33 @@ pub fn find_rat_runs(
(pct * 1000.0) as usize (pct * 1000.0) as usize
}); });
// TODO Heatmap of roads used (any direction) // How many rat-runs pass through each street?
let mut count_per_road = Counter::new();
let mut count_per_intersection = Counter::new();
for path in &paths {
for step in path.get_steps() {
match step {
PathStep::Lane(l) => {
if neighborhood.orig_perimeter.interior.contains(&l.road) {
count_per_road.inc(l.road);
}
}
PathStep::Turn(t) => {
if neighborhood.interior_intersections.contains(&t.parent) {
count_per_intersection.inc(t.parent);
}
}
// Car paths don't make contraflow movements
_ => unreachable!(),
}
}
}
RatRuns { paths } RatRuns {
paths,
count_per_road,
count_per_intersection,
}
} }
struct EntryExit { struct EntryExit {

View File

@ -417,6 +417,9 @@ impl Panel {
None None
} }
} }
pub fn set_checked(&mut self, name: &str, on_off: bool) {
self.find_mut::<Toggle>(name).enabled = on_off
}
pub fn text_box(&self, name: &str) -> String { pub fn text_box(&self, name: &str) -> String {
self.find::<TextBox>(name).get_line() self.find::<TextBox>(name).get_line()