mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +03:00
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:
parent
5542632d61
commit
83bf6953d1
@ -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
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user