Add a layer to color unzoomed agents based on their current delay, to help debug gridlock. Something like this used to exist as a first-class way to change unzoomed color schemes, but until we have more ideas about showing agent intent on the minimap, just implementing this as a separate layer. (#353)

This commit is contained in:
Dustin Carlino 2020-09-29 11:51:37 -07:00 committed by GitHub
parent 4e4e11b404
commit de4f5c7768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 140 additions and 14 deletions

View File

@ -134,6 +134,7 @@ impl PickLayer {
} else {
Widget::nothing()
},
btn("delay per agent", Key::F),
]),
Widget::col(vec![
"Data".draw_text(ctx),
@ -228,6 +229,9 @@ impl State for PickLayer {
"commuter patterns" => {
return Transition::Replace(dashboards::CommuterPatterns::new(ctx, app));
}
"delay per agent" => {
app.layer = Some(Box::new(traffic::DelayPerAgent::new(ctx, app)));
}
_ => unreachable!(),
},
_ => {

View File

@ -2,9 +2,10 @@ use crate::app::App;
use crate::common::{ColorLegend, ColorNetwork, ColorScale, DivergingScale};
use crate::layer::{Layer, LayerOutcome};
use abstutil::Counter;
use geom::{Distance, Duration, Polygon, Time};
use map_model::{IntersectionID, Map, Traversable};
use geom::{Circle, Distance, Duration, Polygon, Pt2D, Time};
use map_model::{IntersectionID, Map, Traversable, NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS};
use maplit::btreeset;
use sim::GetDrawAgents;
use std::collections::BTreeSet;
use widgetry::{
Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line,
@ -596,3 +597,90 @@ impl Jam {
polygons
}
}
pub struct DelayPerAgent {
time: Time,
unzoomed: Drawable,
panel: Panel,
}
impl Layer for DelayPerAgent {
fn name(&self) -> Option<&'static str> {
Some("delay per agent")
}
fn event(
&mut self,
ctx: &mut EventCtx,
app: &mut App,
minimap: &Panel,
) -> Option<LayerOutcome> {
if app.primary.sim.time() != self.time {
*self = DelayPerAgent::new(ctx, app);
}
self.panel.align_above(ctx, minimap);
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"close" => {
return Some(LayerOutcome::Close);
}
_ => unreachable!(),
},
_ => {}
}
None
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
self.panel.draw(g);
if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
g.redraw(&self.unzoomed);
}
}
fn draw_minimap(&self, g: &mut GfxCtx) {
g.redraw(&self.unzoomed);
}
}
impl DelayPerAgent {
pub fn new(ctx: &mut EventCtx, app: &App) -> DelayPerAgent {
let mut delays = app.primary.sim.all_waiting_people();
let mut unzoomed = GeomBatch::new();
unzoomed.push(
app.cs.fade_map_dark,
app.primary.map.get_boundary_polygon().clone(),
);
// A bit of copied code from draw_unzoomed_agents
let car_circle = Circle::new(Pt2D::new(0.0, 0.0), 4.0 * NORMAL_LANE_THICKNESS).to_polygon();
let ped_circle = Circle::new(Pt2D::new(0.0, 0.0), 4.0 * SIDEWALK_THICKNESS).to_polygon();
for agent in app.primary.sim.get_unzoomed_agents(&app.primary.map) {
if let Some(delay) = agent.person.and_then(|p| delays.remove(&p)) {
let color = app
.cs
.good_to_bad_red
.eval((delay / Duration::minutes(15)).min(1.0));
if agent.vehicle_type.is_some() {
unzoomed.push(color, car_circle.translate(agent.pos.x(), agent.pos.y()));
} else {
unzoomed.push(color, ped_circle.translate(agent.pos.x(), agent.pos.y()));
}
}
}
DelayPerAgent {
time: app.primary.sim.time(),
unzoomed: ctx.upload(unzoomed),
panel: Panel::new(Widget::col(vec![
Widget::row(vec![
Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
"Delay per agent (minutes)".draw_text(ctx),
Btn::plaintext("X")
.build(ctx, "close", Key::Escape)
.align_right(),
]),
ColorLegend::gradient(ctx, &app.cs.good_to_bad_red, vec!["0", "5", "10", "15+"]),
]))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
.build(ctx),
}
}
}

View File

@ -264,4 +264,13 @@ impl CarState {
CarState::IdlingAtStop(_, ref time_int) => time_int.end,
}
}
pub fn time_spent_waiting(&self, now: Time) -> Duration {
match self {
CarState::Queued { blocked_since } | CarState::WaitingToAdvance { blocked_since } => {
now - *blocked_since
}
_ => Duration::ZERO,
}
}
}

View File

@ -895,13 +895,7 @@ impl DrivingSimState {
pub fn agent_properties(&self, id: CarID, now: Time) -> AgentProperties {
let car = self.cars.get(&id).unwrap();
let path = car.router.get_path();
let time_spent_waiting = match car.state {
CarState::Queued { blocked_since } | CarState::WaitingToAdvance { blocked_since } => {
now - blocked_since
}
_ => Duration::ZERO,
};
let time_spent_waiting = car.state.time_spent_waiting(now);
AgentProperties {
total_time: now - car.started_at,
@ -1036,4 +1030,15 @@ impl DrivingSimState {
}
affected
}
pub fn all_waiting_people(&self, now: Time, delays: &mut BTreeMap<PersonID, Duration>) {
for c in self.cars.values() {
if let Some((_, person)) = c.trip_and_person {
let delay = c.state.time_spent_waiting(now);
if delay > Duration::ZERO {
delays.insert(person, delay);
}
}
}
}
}

View File

@ -316,12 +316,8 @@ impl WalkingSimState {
pub fn agent_properties(&self, id: PedestrianID, now: Time) -> AgentProperties {
let p = &self.peds[&id];
let time_spent_waiting = match p.state {
PedState::WaitingToTurn(_, blocked_since)
| PedState::WaitingForBus(_, blocked_since) => now - blocked_since,
_ => Duration::ZERO,
};
let time_spent_waiting = p.state.time_spent_waiting(now);
// TODO Incorporate this somewhere
/*if let PedState::WaitingForBus(r, _) = p.state {
extra.push(format!("Waiting for bus {}", map.get_br(r).name));
@ -503,6 +499,15 @@ impl WalkingSimState {
}
affected
}
pub fn all_waiting_people(&self, now: Time, delays: &mut BTreeMap<PersonID, Duration>) {
for p in self.peds.values() {
let delay = p.state.time_spent_waiting(now);
if delay > Duration::ZERO {
delays.insert(p.person, delay);
}
}
}
}
#[derive(Serialize, Deserialize, Clone)]
@ -731,6 +736,14 @@ impl PedState {
PedState::WaitingForBus(_, _) => unreachable!(),
}
}
fn time_spent_waiting(&self, now: Time) -> Duration {
match self {
PedState::WaitingToTurn(_, blocked_since)
| PedState::WaitingForBus(_, blocked_since) => now - *blocked_since,
_ => Duration::ZERO,
}
}
}
// The crowds returned here may have low/high values extending up to radius past the real geometry.

View File

@ -371,6 +371,13 @@ impl Sim {
pub fn infinite_parking(&self) -> bool {
self.parking.is_infinite()
}
pub fn all_waiting_people(&self) -> BTreeMap<PersonID, Duration> {
let mut delays = BTreeMap::new();
self.walking.all_waiting_people(self.time, &mut delays);
self.driving.all_waiting_people(self.time, &mut delays);
delays
}
}
pub struct AgentProperties {