experimenting with tracking and visualizing demand at traffic signals

This commit is contained in:
Dustin Carlino 2019-12-09 15:23:30 -08:00
parent b83e61eaee
commit 2afa2ef43f
8 changed files with 120 additions and 15 deletions

View File

@ -262,6 +262,10 @@ impl State for SandboxMode {
{
let bucket = Duration::hours(1);
self.overlay = Overlays::intersection_delay_over_time(i, bucket, ctx, ui);
} else if ui.primary.map.get_i(i).is_traffic_signal()
&& ctx.input.contextual_action(Key::E, "show current demand")
{
self.overlay = Overlays::intersection_demand(i, ctx, ui);
}
}

View File

@ -8,8 +8,10 @@ use crate::sandbox::bus_explorer::ShowBusRoute;
use crate::sandbox::SandboxMode;
use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Counter};
use ezgui::{Choice, Color, EventCtx, GfxCtx, Key, Line, MenuUnderButton, Text};
use geom::{Duration, Statistic, Time};
use ezgui::{
Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, MenuUnderButton, Text,
};
use geom::{Distance, Duration, PolyLine, Statistic, Time};
use map_model::{IntersectionID, LaneID, PathConstraints, PathStep, RoadID};
use sim::{Analytics, ParkingSpot, TripMode};
use std::collections::{BTreeMap, HashSet};
@ -46,6 +48,7 @@ pub enum Overlays {
i: IntersectionID,
plot: Plot<Duration>,
},
IntersectionDemand(Time, IntersectionID, Drawable),
}
impl Overlays {
@ -117,6 +120,9 @@ impl Overlays {
Overlays::IntersectionDelayOverTime { t, bucket, i, .. } if now != *t => {
*self = Overlays::intersection_delay_over_time(*i, *bucket, ctx, ui);
}
Overlays::IntersectionDemand(t, i, _) if now != *t => {
*self = Overlays::intersection_demand(*i, ctx, ui);
}
Overlays::FinishedTrips(t, _) if now != *t => {
*self = Overlays::finished_trips(ctx, ui);
}
@ -181,6 +187,16 @@ impl Overlays {
plot.draw(g);
true
}
Overlays::IntersectionDemand(_, _, ref draw) => {
ui.draw(
g,
DrawOptions::new(),
&ui.primary.sim,
&ShowEverything::new(),
);
g.redraw(draw);
true
}
Overlays::BusRoute(ref s) => {
s.draw(g, ui);
true
@ -625,6 +641,38 @@ impl Overlays {
}
Overlays::BikePathCosts(colorer.build(ctx, &ui.primary.map))
}
pub fn intersection_demand(i: IntersectionID, ctx: &EventCtx, ui: &UI) -> Overlays {
let mut batch = GeomBatch::new();
let mut total_demand = 0;
let mut demand_per_group: Vec<(&PolyLine, usize)> = Vec::new();
for g in ui.primary.map.get_traffic_signal(i).turn_groups.values() {
let demand = ui
.primary
.sim
.get_analytics()
.thruput_stats
.demand
.get(&g.id)
.cloned()
.unwrap_or(0);
if demand > 0 {
total_demand += demand;
demand_per_group.push((&g.geom, demand));
}
}
for (pl, demand) in demand_per_group {
let percent = (demand as f64) / (total_demand as f64);
batch.push(
Color::RED,
pl.make_arrow(percent * Distance::meters(5.0)).unwrap(),
);
}
Overlays::IntersectionDemand(ui.primary.sim.time(), i, batch.upload(ctx))
}
}
fn color_for_mode(m: TripMode, ui: &UI) -> Color {

View File

@ -4,7 +4,7 @@ use crate::{
connectivity, make, Area, AreaID, Building, BuildingID, BusRoute, BusRouteID, BusStop,
BusStopID, ControlStopSign, ControlTrafficSignal, EditCmd, EditEffects, Intersection,
IntersectionID, IntersectionType, Lane, LaneID, LaneType, MapEdits, Path, PathConstraints,
PathRequest, Position, Road, RoadID, Turn, TurnID, LANE_THICKNESS,
PathRequest, Position, Road, RoadID, Turn, TurnGroupID, TurnID, TurnType, LANE_THICKNESS,
};
use abstutil::{deserialize_btreemap, serialize_btreemap, Error, Timer};
use geom::{Bounds, Distance, GPSBounds, Polygon, Pt2D};
@ -605,6 +605,22 @@ impl Map {
.unwrap()
.should_use_transit(self, start, end)
}
// None for SharedSidewalkCorners
pub fn get_turn_group(&self, t: TurnID) -> Option<TurnGroupID> {
if let Some(ref ts) = self.maybe_get_traffic_signal(t.parent) {
if self.get_t(t).turn_type == TurnType::SharedSidewalkCorner {
return None;
}
for tg in ts.turn_groups.values() {
if tg.members.contains(&t) {
return Some(tg.id);
}
}
unreachable!()
}
None
}
}
impl Map {

View File

@ -3,7 +3,7 @@ use abstutil::Counter;
use derivative::Derivative;
use geom::{Distance, Duration, DurationHistogram, PercentageHistogram, Time};
use map_model::{
BusRouteID, BusStopID, IntersectionID, Map, Path, PathRequest, RoadID, Traversable,
BusRouteID, BusStopID, IntersectionID, Map, Path, PathRequest, RoadID, Traversable, TurnGroupID,
};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, VecDeque};
@ -33,6 +33,9 @@ pub struct ThruputStats {
raw_per_road: Vec<(Time, TripMode, RoadID)>,
raw_per_intersection: Vec<(Time, TripMode, IntersectionID)>,
// Unlike everything else in Analytics, this is just for a moment in time.
pub demand: BTreeMap<TurnGroupID, usize>,
}
impl Analytics {
@ -43,6 +46,7 @@ impl Analytics {
count_per_intersection: Counter::new(),
raw_per_road: Vec::new(),
raw_per_intersection: Vec::new(),
demand: BTreeMap::new(),
},
test_expectations: VecDeque::new(),
bus_arrivals: Vec::new(),
@ -83,6 +87,10 @@ impl Analytics {
.raw_per_intersection
.push((time, mode, t.parent));
}
if let Some(id) = map.get_turn_group(t) {
*self.thruput_stats.demand.entry(id).or_insert(0) -= 1;
}
}
};
}
@ -118,15 +126,33 @@ impl Analytics {
.push((time, delay));
}
// Trip log
if let Event::TripPhaseStarting(id, maybe_req, metadata) = ev {
self.trip_log.push((time, id, maybe_req, metadata));
} else if let Event::TripAborted(id) = ev {
self.trip_log
.push((time, id, None, format!("trip aborted for some reason")));
} else if let Event::TripFinished(id, _, _) = ev {
self.trip_log
.push((time, id, None, format!("trip finished")));
// TODO Kinda hacky, but these all consume the event, so kinda bundle em.
match ev {
Event::TripPhaseStarting(id, maybe_req, metadata) => {
self.trip_log.push((time, id, maybe_req, metadata));
}
Event::TripAborted(id) => {
self.trip_log
.push((time, id, None, format!("trip aborted for some reason")));
}
Event::TripFinished(id, _, _) => {
self.trip_log
.push((time, id, None, format!("trip finished")));
}
Event::PathAmended(path) => {
self.record_demand(&path, map);
}
_ => {}
}
}
pub fn record_demand(&mut self, path: &Path, map: &Map) {
for step in path.get_steps() {
if let Traversable::Turn(t) = step.as_traversable() {
if let Some(id) = map.get_turn_group(t) {
*self.thruput_stats.demand.entry(id).or_insert(0) += 1;
}
}
}
}

View File

@ -1,11 +1,11 @@
use crate::{AgentID, CarID, ParkingSpot, PedestrianID, TripID, TripMode};
use geom::Duration;
use map_model::{
BuildingID, BusRouteID, BusStopID, IntersectionID, LaneID, PathRequest, Traversable,
BuildingID, BusRouteID, BusStopID, IntersectionID, LaneID, Path, PathRequest, Traversable,
};
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum Event {
CarReachedParkingSpot(CarID, ParkingSpot),
CarOrBikeReachedBorder(CarID, IntersectionID),
@ -28,4 +28,8 @@ pub enum Event {
TripFinished(TripID, TripMode, Duration),
TripAborted(TripID),
TripPhaseStarting(TripID, Option<PathRequest>, String),
// Just use for parking replanning. Not happy about copying the full path in here, but the way
// to plumb info into Analytics is Event.
PathAmended(Path),
}

View File

@ -279,6 +279,8 @@ impl DrivingSimState {
}
CarState::Idling(dist, _) => {
car.router = transit.bus_departed_from_stop(car.vehicle.id);
self.events
.push(Event::PathAmended(car.router.get_path().clone()));
car.state = car.crossing_state(dist, now, map);
scheduler.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));

View File

@ -210,6 +210,7 @@ impl Router {
for step in new_path_steps {
self.path.add(step, map);
}
events.push(Event::PathAmended(self.path.clone()));
// TODO This path might not be the same as the one found here...
events.push(Event::TripPhaseStarting(
trip,

View File

@ -273,6 +273,7 @@ impl Sim {
) {
self.trips.agent_starting_trip_leg(AgentID::Car(id), trip);
self.transit.bus_created(id, route.id, next_stop_idx);
self.analytics.record_demand(&path, map);
results.push(id);
return results;
} else {
@ -395,6 +396,8 @@ impl Sim {
Some(req),
format!("{}", create_car.vehicle.id),
));
self.analytics
.record_demand(create_car.router.get_path(), map);
} else if retry_if_no_room {
// TODO Record this in the trip log
self.scheduler.push(
@ -481,6 +484,7 @@ impl Sim {
create_ped.goal.connection
),
));
self.analytics.record_demand(&create_ped.path, map);
// Maybe there's actually no work to do!
match (&create_ped.start.connection, &create_ped.goal.connection) {