very basic tool to point out intersections with the most contention

This commit is contained in:
Dustin Carlino 2019-09-30 11:39:55 -07:00
parent c82e90bbec
commit db4db7e0d9
7 changed files with 111 additions and 30 deletions

View File

@ -3,7 +3,7 @@ use crate::render::{DrawOptions, MIN_ZOOM_FOR_DETAIL};
use crate::ui::{ShowEverything, UI};
use ezgui::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, ScreenPt, Text, LINE_HEIGHT};
use geom::{Distance, Polygon, Pt2D};
use map_model::{BuildingID, LaneID, Map, RoadID};
use map_model::{LaneID, Map, RoadID};
use std::collections::HashMap;
pub struct RoadColorerBuilder {
@ -73,18 +73,18 @@ impl RoadColorerBuilder {
}
}
pub struct BuildingColorerBuilder {
pub struct ObjectColorerBuilder {
zoomed_override_colors: HashMap<ID, Color>,
legend: ColorLegend,
}
pub struct BuildingColorer {
pub struct ObjectColorer {
zoomed_override_colors: HashMap<ID, Color>,
unzoomed: Drawable,
legend: ColorLegend,
}
impl BuildingColorer {
impl ObjectColorer {
pub fn draw(&self, g: &mut GfxCtx, ui: &UI) {
let mut opts = DrawOptions::new();
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
@ -99,28 +99,29 @@ impl BuildingColorer {
}
}
impl BuildingColorerBuilder {
pub fn new(title: &str, rows: Vec<(&str, Color)>) -> BuildingColorerBuilder {
BuildingColorerBuilder {
impl ObjectColorerBuilder {
pub fn new(title: &str, rows: Vec<(&str, Color)>) -> ObjectColorerBuilder {
ObjectColorerBuilder {
zoomed_override_colors: HashMap::new(),
legend: ColorLegend::new(title, rows),
}
}
pub fn add(&mut self, b: BuildingID, color: Color) {
self.zoomed_override_colors.insert(ID::Building(b), color);
pub fn add(&mut self, id: ID, color: Color) {
self.zoomed_override_colors.insert(id, color);
}
pub fn build(self, ctx: &mut EventCtx, map: &Map) -> BuildingColorer {
pub fn build(self, ctx: &mut EventCtx, map: &Map) -> ObjectColorer {
let mut batch = GeomBatch::new();
for (id, color) in &self.zoomed_override_colors {
if let ID::Building(b) = id {
batch.push(*color, map.get_b(*b).polygon.clone());
} else {
unreachable!()
let poly = match id {
ID::Building(b) => map.get_b(*b).polygon.clone(),
ID::Intersection(i) => map.get_i(*i).polygon.clone(),
_ => unreachable!(),
};
batch.push(*color, poly);
}
}
BuildingColorer {
ObjectColorer {
zoomed_override_colors: self.zoomed_override_colors,
unzoomed: ctx.prerender.upload(batch),
legend: self.legend,

View File

@ -13,7 +13,7 @@ mod warp;
pub use self::agent::AgentTools;
pub use self::colors::{
BuildingColorer, BuildingColorerBuilder, ColorLegend, RoadColorer, RoadColorerBuilder,
ColorLegend, ObjectColorer, ObjectColorerBuilder, RoadColorer, RoadColorerBuilder,
};
pub use self::route_explorer::RouteExplorer;
pub use self::speed::SpeedControls;

View File

@ -1,4 +1,4 @@
use crate::common::{BuildingColorer, BuildingColorerBuilder, CommonState, Warping};
use crate::common::{CommonState, ObjectColorer, ObjectColorerBuilder, Warping};
use crate::game::{State, Transition, WizardState};
use crate::helpers::ID;
use crate::mission::pick_time_range;
@ -31,7 +31,7 @@ pub struct ScenarioManager {
cars_needed_per_bldg: HashMap<BuildingID, CarCount>,
total_cars_needed: CarCount,
total_parking_spots: usize,
bldg_colors: BuildingColorer,
bldg_colors: ObjectColorer,
}
impl ScenarioManager {
@ -47,7 +47,7 @@ impl ScenarioManager {
let mut total_cars_needed = CarCount::new();
let color = Color::BLUE;
let mut bldg_colors =
BuildingColorerBuilder::new("trips", vec![("building with trips from/to it", color)]);
ObjectColorerBuilder::new("trips", vec![("building with trips from/to it", color)]);
for (idx, trip) in scenario.individ_trips.iter().enumerate() {
// trips_from_bldg and trips_from_border
match trip {
@ -55,14 +55,14 @@ impl ScenarioManager {
SpawnTrip::CarAppearing { .. } => {}
SpawnTrip::MaybeUsingParkedCar(_, b, _) => {
trips_from_bldg.insert(*b, idx);
bldg_colors.add(*b, color);
bldg_colors.add(ID::Building(*b), color);
}
SpawnTrip::UsingBike(_, ref spot, _)
| SpawnTrip::JustWalking(_, ref spot, _)
| SpawnTrip::UsingTransit(_, ref spot, _, _, _, _) => match spot.connection {
SidewalkPOI::Building(b) => {
trips_from_bldg.insert(b, idx);
bldg_colors.add(b, color);
bldg_colors.add(ID::Building(b), color);
}
SidewalkPOI::Border(i) => {
trips_from_border.insert(i, idx);
@ -78,7 +78,7 @@ impl ScenarioManager {
| SpawnTrip::UsingBike(_, _, ref goal) => match goal {
DrivingGoal::ParkNear(b) => {
trips_to_bldg.insert(*b, idx);
bldg_colors.add(*b, color);
bldg_colors.add(ID::Building(*b), color);
}
DrivingGoal::Border(i, _) => {
trips_to_border.insert(*i, idx);
@ -88,7 +88,7 @@ impl ScenarioManager {
| SpawnTrip::UsingTransit(_, _, ref spot, _, _, _) => match spot.connection {
SidewalkPOI::Building(b) => {
trips_to_bldg.insert(b, idx);
bldg_colors.add(b, color);
bldg_colors.add(ID::Building(b), color);
}
SidewalkPOI::Border(i) => {
trips_to_border.insert(i, idx);

View File

@ -3,8 +3,8 @@ mod spawner;
mod time_travel;
use crate::common::{
time_controls, AgentTools, CommonState, RoadColorer, RoadColorerBuilder, RouteExplorer,
SpeedControls, TripExplorer,
time_controls, AgentTools, CommonState, ObjectColorer, ObjectColorerBuilder, RoadColorer,
RoadColorerBuilder, RouteExplorer, SpeedControls, TripExplorer,
};
use crate::debug::DebugMode;
use crate::edit::EditMode;
@ -26,6 +26,7 @@ pub struct SandboxMode {
pub time_travel: time_travel::InactiveTimeTravel,
common: CommonState,
parking_heatmap: Option<(Duration, RoadColorer)>,
intersection_delay_heatmap: Option<(Duration, ObjectColorer)>,
menu: ModalMenu,
}
@ -37,6 +38,7 @@ impl SandboxMode {
time_travel: time_travel::InactiveTimeTravel::new(),
common: CommonState::new(),
parking_heatmap: None,
intersection_delay_heatmap: None,
menu: ModalMenu::new(
"Sandbox Mode",
vec![
@ -59,6 +61,7 @@ impl SandboxMode {
vec![
// TODO Strange to always have this. Really it's a case of stacked modal?
(hotkey(Key::A), "show/hide parking availability"),
(hotkey(Key::I), "show/hide intersection delay"),
(hotkey(Key::T), "start time traveling"),
(hotkey(Key::Q), "scoreboard"),
],
@ -136,6 +139,27 @@ impl State for SandboxMode {
calculate_parking_heatmap(ctx, &ui.primary),
));
}
if self.menu.action("show/hide intersection delay") {
if self.intersection_delay_heatmap.is_some() {
self.intersection_delay_heatmap = None;
} else {
self.intersection_delay_heatmap = Some((
ui.primary.sim.time(),
calculate_intersection_delay(ctx, &ui.primary),
));
}
}
if self
.intersection_delay_heatmap
.as_ref()
.map(|(t, _)| *t != ui.primary.sim.time())
.unwrap_or(false)
{
self.intersection_delay_heatmap = Some((
ui.primary.sim.time(),
calculate_intersection_delay(ctx, &ui.primary),
));
}
if self.menu.action("quit") {
return Transition::Pop;
@ -243,8 +267,11 @@ impl State for SandboxMode {
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
// TODO Oh no, these're actually exclusive, represent that better.
if let Some((_, ref c)) = self.parking_heatmap {
c.draw(g, ui);
} else if let Some((_, ref c)) = self.intersection_delay_heatmap {
c.draw(g, ui);
} else {
ui.draw(
g,
@ -338,3 +365,29 @@ fn calculate_parking_heatmap(ctx: &mut EventCtx, primary: &PerMapUI) -> RoadColo
colorer.build(ctx, &primary.map)
}
fn calculate_intersection_delay(ctx: &mut EventCtx, primary: &PerMapUI) -> ObjectColorer {
let fast = Color::GREEN;
let meh = Color::YELLOW;
let slow = Color::RED;
let mut colorer = ObjectColorerBuilder::new(
"intersection delay (90%ile)",
vec![("< 10s", fast), ("<= 60s", meh), ("> 60s", slow)],
);
for i in primary.map.all_intersections() {
let delays = primary.sim.get_intersection_delays(i.id);
if let Some(d) = delays.percentile(90.0) {
let color = if d < Duration::seconds(10.0) {
fast
} else if d <= Duration::seconds(60.0) {
meh
} else {
slow
};
colorer.add(ID::Intersection(i.id), color);
}
}
colorer.build(ctx, &primary.map)
}

View File

@ -259,6 +259,10 @@ impl DurationHistogram {
}
pub fn describe(&self) -> String {
if self.count == 0 {
return "no data yet".to_string();
}
format!(
"{} count, 50%ile {}, 90%ile {}, 99%ile {}",
abstutil::prettyprint_usize(self.count),
@ -267,4 +271,12 @@ impl DurationHistogram {
Duration::from_u64(self.histogram.percentile(99.0).unwrap()),
)
}
// None if empty
pub fn percentile(&self, p: f64) -> Option<Duration> {
if self.count == 0 {
return None;
}
Some(Duration::from_u64(self.histogram.percentile(p).unwrap()))
}
}

View File

@ -2,7 +2,8 @@ use crate::mechanics::car::Car;
use crate::mechanics::queue::Queue;
use crate::{AgentID, Command, Scheduler, Speed};
use abstutil::{deserialize_btreemap, serialize_btreemap};
use geom::Duration;
use derivative::Derivative;
use geom::{Duration, DurationHistogram};
use map_model::{
ControlStopSign, ControlTrafficSignal, IntersectionID, LaneID, Map, TurnID, TurnPriority,
TurnType,
@ -19,7 +20,8 @@ pub struct IntersectionSimState {
force_queue_entry: bool,
}
#[derive(Serialize, Deserialize, PartialEq)]
#[derive(Serialize, Deserialize, Derivative)]
#[derivative(PartialEq)]
struct State {
id: IntersectionID,
accepted: BTreeSet<Request>,
@ -29,6 +31,9 @@ struct State {
deserialize_with = "deserialize_btreemap"
)]
waiting: BTreeMap<Request, Duration>,
#[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)]
delays: DurationHistogram,
}
impl IntersectionSimState {
@ -50,6 +55,7 @@ impl IntersectionSimState {
id: i.id,
accepted: BTreeSet::new(),
waiting: BTreeMap::new(),
delays: std::default::Default::default(),
},
);
if i.is_traffic_signal() {
@ -169,7 +175,7 @@ impl IntersectionSimState {
}
assert!(!state.any_accepted_conflict_with(turn, map));
state.waiting.remove(&req).unwrap();
state.delays.add(now - state.waiting.remove(&req).unwrap());
state.accepted.insert(req);
true
}
@ -183,6 +189,7 @@ impl IntersectionSimState {
} else {
println!("Border");
}
println!("Delays: {}", self.state[&id].delays.describe());
}
pub fn get_accepted_agents(&self, id: IntersectionID) -> HashSet<AgentID> {
@ -192,6 +199,10 @@ impl IntersectionSimState {
.map(|req| req.agent)
.collect()
}
pub fn get_intersection_delays(&self, id: IntersectionID) -> &DurationHistogram {
&self.state[&id].delays
}
}
impl State {

View File

@ -8,7 +8,7 @@ use crate::{
};
use abstutil::{elapsed_seconds, Timer};
use derivative::Derivative;
use geom::{Distance, Duration, PolyLine, Pt2D};
use geom::{Distance, Duration, DurationHistogram, PolyLine, Pt2D};
use map_model::{
BuildingID, BusRoute, BusRouteID, IntersectionID, LaneID, Map, Path, PathRequest, Position,
Traversable,
@ -892,6 +892,10 @@ impl Sim {
pub fn get_accepted_agents(&self, id: IntersectionID) -> HashSet<AgentID> {
self.intersections.get_accepted_agents(id)
}
pub fn get_intersection_delays(&self, id: IntersectionID) -> &DurationHistogram {
self.intersections.get_intersection_delays(id)
}
}
// Invasive debugging