mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
very basic tool to point out intersections with the most contention
This commit is contained in:
parent
c82e90bbec
commit
db4db7e0d9
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user