basic heatmap showing busiest roads and intersections. have to revive

Sim Events kind of.
This commit is contained in:
Dustin Carlino 2019-10-04 15:33:26 -07:00
parent 9d35f0f92f
commit 1a10e8982a
13 changed files with 212 additions and 49 deletions

View File

@ -75,6 +75,12 @@ impl<T: Ord + PartialEq> Counter<T> {
pub fn get(&self, val: T) -> usize {
self.map.get(&val).cloned().unwrap_or(0)
}
pub fn sorted_asc(&self) -> Vec<&T> {
let mut list = self.map.iter().collect::<Vec<_>>();
list.sort_by_key(|(_, cnt)| *cnt);
list.into_iter().map(|(t, _)| t).collect()
}
}
pub fn wraparound_get<T>(vec: &Vec<T>, idx: isize) -> &T {

View File

@ -3,7 +3,6 @@
- enable more clippy lints
- enforce consistent style (derive order, struct initialization order)
- update with mission statement (democratized urb p, that quote, refashion existing space cheaply)
- trailer
- show common parts of routes in A/B, point of divergence
- "Two parallel universes sit at your fingertips, and with the flick of a key, you can glide between the two. Buses jumping past traffic in one world, snarly traffic jam in the other. An A/B test revealing what currently is, and what could be, compared meticulously and deterministically. A/B Street -- which world do you prefer?"

View File

@ -22,9 +22,6 @@
- deal with loop roads?
- model U-turns
- degenerate-2's should only have one crosswalk
- then make them thinner
- car turns often clip sidewalk corners now
- draw SharedSidewalkCorners just around the ped path, not arbitrarily thick
- dont forget to draw the notches
@ -84,10 +81,6 @@
- just revert intersection and warn
- or store overrides more granularly and warn or do something reasonable
## Release
- publish the map data
## Sim bugs/tests needed
- do bikes use bike lanes?
@ -95,11 +88,11 @@
- make sure that we can jump to a ped on a bus and see the bus
- park/unpark needs to jump two lanes in the case of crossing a bike lane or something
- should only be able to park from the closest lane, though!
- explicit tests making cars park at 0 and max_dist, peds walk to 0 and max_dist
## Discrete-event sim model
- cleanup after the cutover
- explicit tests making cars park at 0 and max_dist, peds walk to 0 and max_dist
- proper intersection policies, by seeing full view
- time travel mode can be very smart
- dupe code for initially spawning vs spawning when a trip leg starts.

View File

@ -2,9 +2,6 @@
## Fix existing stuff
- try showing traffic signals by little boxes at the end of lanes
- red circle means right turn on red OK, red right arrow means nope, green means normal turns ok, green arrow means protected left, crosswalk hand or stick figure
- if a lane could feasibly have multiple turn options but doesnt, print "ONLY"
- audit all panics
- tune text color, size, padding
@ -15,8 +12,6 @@
- yellow or flashing red/yellow for yields
- text box entry: highlight char looks like replace mode; draw it btwn chars
- traffic signal cycles go offscreen sometimes!
- navigator
- show options on map
- stop jumping text size
@ -27,7 +22,6 @@
- when dragging, dont give mouse movement to UI elements
- start context menu when left click releases and we're not dragging
- can we change labels in modal or top menu? show/hide
- label sections of modal menus
- distinguish hints from status of modal menus, for hiding purposes
- move context menus out of ezgui
- simplify/remove UserInput.
@ -43,8 +37,6 @@
## Better rendering
- depict residential bldg occupany size somehow
- render overlapping peds reasonably
- draw moving / blocked colors (gradually more red as they wait longer)
- render cars with textures?
- rooftops
- https://thumbs.dreamstime.com/b/top-view-city-street-asphalt-transport-people-walking-down-sidewalk-intersecting-road-pedestrian-81034411.jpg

View File

@ -76,6 +76,7 @@ impl RoadColorerBuilder {
pub struct ObjectColorerBuilder {
zoomed_override_colors: HashMap<ID, Color>,
legend: ColorLegend,
roads: Vec<(RoadID, Color)>,
}
pub struct ObjectColorer {
@ -104,14 +105,19 @@ impl ObjectColorerBuilder {
ObjectColorerBuilder {
zoomed_override_colors: HashMap::new(),
legend: ColorLegend::new(title, rows),
roads: Vec::new(),
}
}
pub fn add(&mut self, id: ID, color: Color) {
self.zoomed_override_colors.insert(id, color);
if let ID::Road(r) = id {
self.roads.push((r, color));
} else {
self.zoomed_override_colors.insert(id, color);
}
}
pub fn build(self, ctx: &mut EventCtx, map: &Map) -> ObjectColorer {
pub fn build(mut self, ctx: &mut EventCtx, map: &Map) -> ObjectColorer {
let mut batch = GeomBatch::new();
for (id, color) in &self.zoomed_override_colors {
let poly = match id {
@ -121,6 +127,12 @@ impl ObjectColorerBuilder {
};
batch.push(*color, poly);
}
for (r, color) in self.roads {
batch.push(color, map.get_r(r).get_thick_polygon().unwrap());
for l in map.get_r(r).all_lanes() {
self.zoomed_override_colors.insert(ID::Lane(l), color);
}
}
ObjectColorer {
zoomed_override_colors: self.zoomed_override_colors,
unzoomed: ctx.prerender.upload(batch),

View File

@ -1,7 +1,8 @@
mod score;
mod spawner;
mod stats;
mod thruput_stats;
mod time_travel;
mod trip_stats;
use crate::common::{
time_controls, AgentTools, CommonState, ObjectColorer, ObjectColorerBuilder, RoadColorer,
@ -25,7 +26,8 @@ pub struct SandboxMode {
speed: SpeedControls,
agent_tools: AgentTools,
pub time_travel: time_travel::InactiveTimeTravel,
stats: stats::TripStats,
trip_stats: trip_stats::TripStats,
thruput_stats: thruput_stats::ThruputStats,
common: CommonState,
parking_heatmap: Option<(Duration, RoadColorer)>,
intersection_delay_heatmap: Option<(Duration, ObjectColorer)>,
@ -38,7 +40,10 @@ impl SandboxMode {
speed: SpeedControls::new(ctx, None),
agent_tools: AgentTools::new(ctx),
time_travel: time_travel::InactiveTimeTravel::new(),
stats: stats::TripStats::new(ui.primary.current_flags.sim_flags.opts.record_stats),
trip_stats: trip_stats::TripStats::new(
ui.primary.current_flags.sim_flags.opts.record_stats,
),
thruput_stats: thruput_stats::ThruputStats::new(),
common: CommonState::new(),
parking_heatmap: None,
intersection_delay_heatmap: None,
@ -68,6 +73,7 @@ impl SandboxMode {
(hotkey(Key::T), "start time traveling"),
(hotkey(Key::Q), "scoreboard"),
(None, "trip stats"),
(None, "throughput stats"),
],
vec![
(hotkey(Key::Escape), "quit"),
@ -88,7 +94,8 @@ impl SandboxMode {
impl State for SandboxMode {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
self.time_travel.record(ui);
self.stats.record(ui);
self.trip_stats.record(ui);
self.thruput_stats.record(ui);
{
let mut txt = Text::prompt("Sandbox Mode");
@ -124,7 +131,18 @@ impl State for SandboxMode {
return Transition::Push(Box::new(score::Scoreboard::new(ctx, ui)));
}
if self.menu.action("trip stats") {
return Transition::Push(Box::new(stats::ShowStats::new(&self.stats, ui, ctx)));
if let Some(s) = trip_stats::ShowStats::new(&self.trip_stats, ui, ctx) {
return Transition::Push(Box::new(s));
} else {
println!("No trip stats available");
}
}
if self.menu.action("throughput stats") {
return Transition::Push(Box::new(thruput_stats::ShowStats::new(
&self.thruput_stats,
ui,
ctx,
)));
}
if self.menu.action("show/hide parking availability") {
if self.parking_heatmap.is_some() {

View File

@ -0,0 +1,123 @@
use crate::common::{ObjectColorer, ObjectColorerBuilder};
use crate::game::{State, Transition};
use crate::helpers::ID;
use crate::ui::UI;
use abstutil::Counter;
use ezgui::{hotkey, Color, EventCtx, GfxCtx, Key, ModalMenu};
use map_model::{IntersectionID, RoadID, Traversable};
use sim::Event;
pub struct ThruputStats {
count_per_road: Counter<RoadID>,
count_per_intersection: Counter<IntersectionID>,
}
impl ThruputStats {
pub fn new() -> ThruputStats {
ThruputStats {
count_per_road: Counter::new(),
count_per_intersection: Counter::new(),
}
}
pub fn record(&mut self, ui: &mut UI) {
for ev in ui.primary.sim.collect_events() {
if let Event::AgentEntersTraversable(_, to) = ev {
match to {
Traversable::Lane(l) => self.count_per_road.inc(ui.primary.map.get_l(l).parent),
Traversable::Turn(t) => self.count_per_intersection.inc(t.parent),
};
}
}
}
}
pub struct ShowStats {
menu: ModalMenu,
heatmap: ObjectColorer,
}
impl State for ShowStats {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
ui.recalculate_current_selection(ctx);
}
self.menu.handle_event(ctx, None);
if self.menu.action("quit") {
return Transition::Pop;
}
Transition::Keep
}
fn draw_default_ui(&self) -> bool {
false
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
self.heatmap.draw(g, ui);
self.menu.draw(g);
}
}
impl ShowStats {
pub fn new(stats: &ThruputStats, ui: &UI, ctx: &mut EventCtx) -> ShowStats {
let light = Color::GREEN;
let medium = Color::YELLOW;
let heavy = Color::RED;
let mut colorer = ObjectColorerBuilder::new(
"Throughput",
vec![
("< 50%ile", light),
("< 90%ile", medium),
(">= 90%ile", heavy),
],
);
// TODO If there are many duplicate counts, arbitrarily some will look heavier! Find the
// disribution of counts instead.
// TODO Actually display the counts at these percentiles
// TODO Dump the data in debug mode
{
let roads = stats.count_per_road.sorted_asc();
let p50_idx = ((roads.len() as f64) * 0.5) as usize;
let p90_idx = ((roads.len() as f64) * 0.9) as usize;
for (idx, r) in roads.into_iter().enumerate() {
let color = if idx < p50_idx {
light
} else if idx < p90_idx {
medium
} else {
heavy
};
colorer.add(ID::Road(*r), color);
}
}
// TODO dedupe
{
let intersections = stats.count_per_intersection.sorted_asc();
let p50_idx = ((intersections.len() as f64) * 0.5) as usize;
let p90_idx = ((intersections.len() as f64) * 0.9) as usize;
for (idx, i) in intersections.into_iter().enumerate() {
let color = if idx < p50_idx {
light
} else if idx < p90_idx {
medium
} else {
heavy
};
colorer.add(ID::Intersection(*i), color);
}
}
ShowStats {
menu: ModalMenu::new(
"Thruput Stats",
vec![vec![(hotkey(Key::Escape), "quit")]],
ctx,
),
heatmap: colorer.build(ctx, &ui.primary.map),
}
}
}

View File

@ -104,7 +104,11 @@ impl State for ShowStats {
}
impl ShowStats {
pub fn new(stats: &TripStats, ui: &UI, ctx: &mut EventCtx) -> ShowStats {
pub fn new(stats: &TripStats, ui: &UI, ctx: &mut EventCtx) -> Option<ShowStats> {
if stats.samples.is_empty() {
return None;
}
let mut batch = GeomBatch::new();
let mut labels = MultiText::new();
@ -184,9 +188,6 @@ impl ShowStats {
}
for (_, color, getter) in lines {
if stats.samples.is_empty() {
continue;
}
let mut pts = Vec::new();
if max_y == 0 {
pts.push(Pt2D::new(x1, y2));
@ -217,12 +218,12 @@ impl ShowStats {
"{} samples",
abstutil::prettyprint_usize(stats.samples.len())
)));
ShowStats {
Some(ShowStats {
menu: ModalMenu::new("Trip Stats", vec![vec![(hotkey(Key::Escape), "quit")]], ctx)
.set_prompt(ctx, txt),
draw: ctx.prerender.upload(batch),
labels,
legend,
}
})
}
}

View File

@ -19,6 +19,5 @@ pub enum Event {
BikeStoppedAtSidewalk(CarID, LaneID),
// TODO Remove this one
AgentEntersTraversable(AgentID, Traversable),
}

View File

@ -1,7 +1,7 @@
use crate::mechanics::car::{Car, CarState};
use crate::mechanics::queue::Queue;
use crate::{
ActionAtEnd, AgentID, CarID, Command, CreateCar, DistanceInterval, DrawCarInput,
ActionAtEnd, AgentID, CarID, Command, CreateCar, DistanceInterval, DrawCarInput, Event,
IntersectionSimState, ParkedCar, ParkingSimState, Scheduler, TimeInterval, TransitSimState,
TripManager, TripPositions, UnzoomedAgent, WalkingSimState, FOLLOWING_DISTANCE,
};
@ -32,6 +32,7 @@ pub struct DrivingSimState {
deserialize_with = "deserialize_btreemap"
)]
queues: BTreeMap<Traversable, Queue>,
events: Vec<Event>,
}
impl DrivingSimState {
@ -39,6 +40,7 @@ impl DrivingSimState {
let mut sim = DrivingSimState {
cars: BTreeMap::new(),
queues: BTreeMap::new(),
events: Vec::new(),
};
for l in map.all_lanes() {
@ -342,6 +344,10 @@ impl DrivingSimState {
car.state = car.crossing_state(Distance::ZERO, now, map);
car.blocked_since = None;
scheduler.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));
self.events.push(Event::AgentEntersTraversable(
AgentID::Car(car.vehicle.id),
goto,
));
car.last_steps.push_front(last_step);
if goto.length(map) >= car.vehicle.length + FOLLOWING_DISTANCE {
@ -899,4 +905,8 @@ impl DrivingSimState {
}
false
}
pub fn collect_events(&mut self) -> Vec<Event> {
std::mem::replace(&mut self.events, Vec::new())
}
}

View File

@ -1,6 +1,6 @@
use crate::{
AgentID, AgentMetadata, Command, CreatePedestrian, DistanceInterval, DrawPedCrowdInput,
DrawPedestrianInput, IntersectionSimState, ParkingSimState, ParkingSpot, PedestrianID,
DrawPedestrianInput, Event, IntersectionSimState, ParkingSimState, ParkingSpot, PedestrianID,
Scheduler, SidewalkPOI, SidewalkSpot, TimeInterval, TransitSimState, TripID, TripManager,
TripPositions, UnzoomedAgent,
};
@ -22,6 +22,7 @@ pub struct WalkingSimState {
deserialize_with = "deserialize_multimap"
)]
peds_per_traversable: MultiMap<Traversable, PedestrianID>,
events: Vec<Event>,
}
impl WalkingSimState {
@ -29,6 +30,7 @@ impl WalkingSimState {
WalkingSimState {
peds: BTreeMap::new(),
peds_per_traversable: MultiMap::new(),
events: Vec::new(),
}
}
@ -164,6 +166,7 @@ impl WalkingSimState {
map,
intersections,
&mut self.peds_per_traversable,
&mut self.events,
scheduler,
) {
scheduler.push(ped.state.get_end_time(), Command::UpdatePed(ped.id));
@ -180,6 +183,7 @@ impl WalkingSimState {
map,
intersections,
&mut self.peds_per_traversable,
&mut self.events,
scheduler,
) {
scheduler.push(ped.state.get_end_time(), Command::UpdatePed(ped.id));
@ -377,6 +381,10 @@ impl WalkingSimState {
(loners, crowds)
}
pub fn collect_events(&mut self) -> Vec<Event> {
std::mem::replace(&mut self.events, Vec::new())
}
}
#[derive(Serialize, Deserialize, PartialEq)]
@ -529,6 +537,7 @@ impl Pedestrian {
map: &Map,
intersections: &mut IntersectionSimState,
peds_per_traversable: &mut MultiMap<Traversable, PedestrianID>,
events: &mut Vec<Event>,
scheduler: &mut Scheduler,
) -> bool {
if let PathStep::Turn(t) = self.path.next_step() {
@ -554,6 +563,10 @@ impl Pedestrian {
};
self.state = self.crossing_state(start_dist, now, map);
peds_per_traversable.insert(self.path.current_step().as_traversable(), self.id);
events.push(Event::AgentEntersTraversable(
AgentID::Pedestrian(self.id),
self.path.current_step().as_traversable(),
));
true
}
}

View File

@ -50,10 +50,7 @@ pub struct Sim {
#[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)]
trip_positions: Option<TripPositions>,
#[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)]
events_since_last_step: Vec<Event>,
// TODO Maybe the buffered events in child objects should also have this.
}
#[derive(Clone)]
@ -112,7 +109,6 @@ impl Sim {
run_name: opts.run_name,
step_count: 0,
trip_positions: None,
events_since_last_step: Vec::new(),
}
}
@ -518,12 +514,6 @@ impl Sim {
self.time = target_time;
self.trip_positions = None;
self.events_since_last_step.clear();
self.events_since_last_step
.extend(self.trips.collect_events());
self.events_since_last_step
.extend(self.transit.collect_events());
}
pub fn timed_step(&mut self, map: &Map, dt: Duration, timer: &mut Timer) {
@ -646,8 +636,8 @@ impl Sim {
let mut expectations = VecDeque::from(all_expectations);
self.step(&map, self.time() + time_limit);
for ev in self.get_events_since_last_step() {
if ev == expectations.front().unwrap() {
for ev in self.collect_events() {
if &ev == expectations.front().unwrap() {
println!("At {}, met expectation {:?}", self.time, ev);
expectations.pop_front();
if expectations.is_empty() {
@ -864,8 +854,15 @@ impl Sim {
self.trip_positions.as_ref().unwrap()
}
pub fn get_events_since_last_step(&self) -> &Vec<Event> {
&self.events_since_last_step
// This only supports one caller! And the result isn't time-sorted.
// TODO If nobody calls this, slow sad memory leak. Push style would probably be much nicer.
pub fn collect_events(&mut self) -> Vec<Event> {
let mut events = Vec::new();
events.extend(self.trips.collect_events());
events.extend(self.transit.collect_events());
events.extend(self.driving.collect_events());
events.extend(self.walking.collect_events());
events
}
pub fn get_canonical_pt_per_trip(&self, trip: TripID, map: &Map) -> TripResult<Pt2D> {

View File

@ -483,7 +483,7 @@ impl TripManager {
}
pub fn collect_events(&mut self) -> Vec<Event> {
self.events.drain(..).collect()
std::mem::replace(&mut self.events, Vec::new())
}
pub fn trip_status(&self, id: TripID) -> TripStatus {