collect and draw info on road throughput per some bucket of time. much

work needed.
This commit is contained in:
Dustin Carlino 2019-11-10 14:38:26 -08:00
parent 0fb358b38d
commit b24100caba
5 changed files with 311 additions and 70 deletions

View File

@ -2,6 +2,7 @@ use crate::common::CommonState;
use crate::game::{State, Transition};
use crate::helpers::ID;
use crate::ui::UI;
use abstutil::prettyprint_usize;
use ezgui::{hotkey, Color, EventCtx, GfxCtx, Key, Line, ModalMenu, Text};
use geom::Duration;
use sim::CarID;
@ -106,6 +107,12 @@ fn info_for(id: ID, ui: &UI, ctx: &EventCtx) -> Text {
to, restriction
)));
}
txt.add(Line(""));
txt.add(Line(format!(
"{} total agents crossed",
prettyprint_usize(sim.get_analytics().thruput_stats.count_per_road.get(r.id))
)));
}
ID::Intersection(id) => {
let i = map.get_i(id);
@ -146,6 +153,17 @@ fn info_for(id: ID, ui: &UI, ctx: &EventCtx) -> Text {
txt.add(Line(line));
}
}
txt.add(Line(""));
txt.add(Line(format!(
"{} total agents crossed",
prettyprint_usize(
sim.get_analytics()
.thruput_stats
.count_per_intersection
.get(id)
)
)));
}
// TODO No way to trigger the info panel for this yet.
ID::Turn(id) => {
@ -238,7 +256,7 @@ fn info_for(id: ID, ui: &UI, ctx: &EventCtx) -> Text {
}
txt.add(Line(format!(
" {} passengers total (any stop)",
abstutil::prettyprint_usize(passengers.get(r.id))
prettyprint_usize(passengers.get(r.id))
)));
}
}

View File

@ -172,7 +172,11 @@ impl Yvalue<usize> for usize {
((*self as f64) * percent) as usize
}
fn to_percent(self, max: usize) -> f64 {
(self as f64) / (max as f64)
if max == 0 {
0.0
} else {
(self as f64) / (max as f64)
}
}
fn prettyprint(self) -> String {
abstutil::prettyprint_usize(self)
@ -183,7 +187,11 @@ impl Yvalue<Duration> for Duration {
percent * *self
}
fn to_percent(self, max: Duration) -> f64 {
self / max
if max == Duration::ZERO {
0.0
} else {
self / max
}
}
fn prettyprint(self) -> String {
self.minimal_tostring()

View File

@ -205,6 +205,37 @@ impl State for SandboxMode {
})));
}
}
if let Some(ID::Lane(l)) = ui.primary.current_selection {
if ctx
.input
.contextual_action(Key::T, "throughput over 1-hour buckets")
{
let r = ui.primary.map.get_l(l).parent;
let t = ui.primary.sim.time();
let bucket = Duration::minutes(60);
self.overlay = overlays::Overlays::RoadThroughput {
t,
bucket,
r,
plot: overlays::calculate_road_thruput(r, bucket, ctx, ui),
};
}
}
if let Some(ID::Intersection(i)) = ui.primary.current_selection {
if ctx
.input
.contextual_action(Key::T, "throughput over 1-hour buckets")
{
let t = ui.primary.sim.time();
let bucket = Duration::minutes(60);
self.overlay = overlays::Overlays::IntersectionThroughput {
t,
bucket,
i,
plot: overlays::calculate_intersection_thruput(i, bucket, ctx, ui),
};
}
}
if self.save_tools.action("save sim state") {
self.speed.pause();

View File

@ -10,7 +10,7 @@ use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Counter};
use ezgui::{Choice, Color, EventCtx, GfxCtx, Key, Line, MenuUnderButton, Text};
use geom::Duration;
use map_model::{LaneType, PathStep};
use map_model::{IntersectionID, LaneType, PathStep, RoadID};
use sim::{ParkingSpot, TripMode};
use std::collections::{BTreeMap, HashSet};
@ -19,6 +19,18 @@ pub enum Overlays {
ParkingAvailability(Duration, RoadColorer),
IntersectionDelay(Duration, ObjectColorer),
Throughput(Duration, ObjectColorer),
RoadThroughput {
t: Duration,
bucket: Duration,
r: RoadID,
plot: Plot<usize>,
},
IntersectionThroughput {
t: Duration,
bucket: Duration,
i: IntersectionID,
plot: Plot<usize>,
},
FinishedTrips(Duration, Plot<usize>),
Chokepoints(Duration, ObjectColorer),
BikeNetwork(RoadColorer),
@ -54,31 +66,79 @@ impl Overlays {
})?;
Some(Transition::PopWithData(Box::new(move |state, ui, ctx| {
let mut sandbox = state.downcast_mut::<SandboxMode>().unwrap();
sandbox.overlay = Overlays::recalc(&choice, ui, ctx);
let time = ui.primary.sim.time();
sandbox.overlay = match choice.as_ref() {
"none" => Overlays::Inactive,
"parking availability" => Overlays::ParkingAvailability(
time,
calculate_parking_heatmap(ctx, ui),
),
"intersection delay" => Overlays::IntersectionDelay(
time,
calculate_intersection_delay(ctx, ui),
),
"cumulative throughput" => {
Overlays::Throughput(time, calculate_thruput(ctx, ui))
}
"finished trips" => Overlays::FinishedTrips(time, trip_stats(ctx, ui)),
"chokepoints" => {
Overlays::Chokepoints(time, calculate_chokepoints(ctx, ui))
}
"bike network" => {
Overlays::BikeNetwork(calculate_bike_network(ctx, ui))
}
"bus network" => Overlays::BusNetwork(calculate_bus_network(ctx, ui)),
_ => unreachable!(),
};
})))
},
))));
}
let (choice, time) = match self {
Overlays::Inactive => {
return None;
let now = ui.primary.sim.time();
match self {
// Don't bother with Inactive, BusRoute, BusDelaysOverTime, BikeNetwork, BusNetwork --
// nothing needed or the gameplay mode will update it.
Overlays::ParkingAvailability(ref mut t, ref mut x) if now != *t => {
*t = now;
*x = calculate_parking_heatmap(ctx, ui);
}
Overlays::ParkingAvailability(t, _) => ("parking availability", *t),
Overlays::IntersectionDelay(t, _) => ("intersection delay", *t),
Overlays::Throughput(t, _) => ("cumulative throughput", *t),
Overlays::FinishedTrips(t, _) => ("finished trips", *t),
Overlays::Chokepoints(t, _) => ("chokepoints", *t),
Overlays::BikeNetwork(_) => ("bike network", ui.primary.sim.time()),
Overlays::BusNetwork(_) => ("bus network", ui.primary.sim.time()),
Overlays::BusRoute(_) | Overlays::BusDelaysOverTime(_) => {
// The gameplay mode will update it.
return None;
Overlays::IntersectionDelay(t, x) if now != *t => {
*t = now;
*x = calculate_intersection_delay(ctx, ui);
}
Overlays::Throughput(t, x) if now != *t => {
*t = now;
*x = calculate_thruput(ctx, ui);
}
Overlays::FinishedTrips(t, x) if now != *t => {
*t = now;
*x = trip_stats(ctx, ui);
}
Overlays::RoadThroughput {
ref mut t,
bucket,
r,
ref mut plot,
} if now != *t => {
*t = now;
*plot = calculate_road_thruput(*r, *bucket, ctx, ui);
}
Overlays::IntersectionThroughput {
ref mut t,
bucket,
i,
ref mut plot,
} if now != *t => {
*t = now;
*plot = calculate_intersection_thruput(*i, *bucket, ctx, ui);
}
Overlays::Chokepoints(t, x) if now != *t => {
*t = now;
*x = calculate_chokepoints(ctx, ui);
}
_ => {}
};
if time != ui.primary.sim.time() {
*self = Overlays::recalc(choice, ui, ctx);
}
None
}
@ -98,14 +158,16 @@ impl Overlays {
heatmap.draw(g, ui);
true
}
Overlays::FinishedTrips(_, ref s) => {
Overlays::RoadThroughput { ref plot, .. }
| Overlays::IntersectionThroughput { ref plot, .. }
| Overlays::FinishedTrips(_, ref plot) => {
ui.draw(
g,
DrawOptions::new(),
&ui.primary.sim,
&ShowEverything::new(),
);
s.draw(g);
plot.draw(g);
true
}
Overlays::BusDelaysOverTime(ref s) => {
@ -124,28 +186,9 @@ impl Overlays {
}
}
}
fn recalc(choice: &str, ui: &UI, ctx: &mut EventCtx) -> Overlays {
let time = ui.primary.sim.time();
match choice {
"none" => Overlays::Inactive,
"parking availability" => {
Overlays::ParkingAvailability(time, calculate_parking_heatmap(ctx, ui))
}
"intersection delay" => {
Overlays::IntersectionDelay(time, calculate_intersection_delay(ctx, ui))
}
"cumulative throughput" => Overlays::Throughput(time, calculate_thruput(ctx, ui)),
"finished trips" => Overlays::FinishedTrips(time, trip_stats(ui, ctx)),
"chokepoints" => Overlays::Chokepoints(time, calculate_chokepoints(ctx, ui)),
"bike network" => Overlays::BikeNetwork(calculate_bike_network(ctx, ui)),
"bus network" => Overlays::BusNetwork(calculate_bus_network(ctx, ui)),
_ => unreachable!(),
}
}
}
fn calculate_parking_heatmap(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
fn calculate_parking_heatmap(ctx: &EventCtx, ui: &UI) -> RoadColorer {
let (filled_spots, avail_spots) = ui.primary.sim.get_all_parking_spots();
let mut txt = Text::prompt("parking availability");
txt.add(Line(format!(
@ -217,7 +260,7 @@ fn calculate_parking_heatmap(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
colorer.build(ctx, &ui.primary.map)
}
fn calculate_intersection_delay(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
fn calculate_intersection_delay(ctx: &EventCtx, ui: &UI) -> ObjectColorer {
let fast = Color::GREEN;
let meh = Color::YELLOW;
let slow = Color::RED;
@ -243,7 +286,7 @@ fn calculate_intersection_delay(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
colorer.build(ctx, &ui.primary.map)
}
fn calculate_chokepoints(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
fn calculate_chokepoints(ctx: &EventCtx, ui: &UI) -> ObjectColorer {
const TOP_N: usize = 10;
let mut colorer = ObjectColorerBuilder::new(
@ -285,7 +328,7 @@ fn calculate_chokepoints(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
colorer.build(ctx, &ui.primary.map)
}
fn calculate_thruput(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
fn calculate_thruput(ctx: &EventCtx, ui: &UI) -> ObjectColorer {
let light = Color::GREEN;
let medium = Color::YELLOW;
let heavy = Color::RED;
@ -339,7 +382,7 @@ fn calculate_thruput(ctx: &mut EventCtx, ui: &UI) -> ObjectColorer {
colorer.build(ctx, &ui.primary.map)
}
fn calculate_bike_network(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
fn calculate_bike_network(ctx: &EventCtx, ui: &UI) -> RoadColorer {
let mut colorer = RoadColorerBuilder::new(
Text::prompt("bike networks"),
vec![("bike lanes", Color::GREEN)],
@ -352,7 +395,7 @@ fn calculate_bike_network(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
colorer.build(ctx, &ui.primary.map)
}
fn calculate_bus_network(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
fn calculate_bus_network(ctx: &EventCtx, ui: &UI) -> RoadColorer {
let mut colorer = RoadColorerBuilder::new(
Text::prompt("bus networks"),
vec![("bike lanes", Color::GREEN)],
@ -365,22 +408,21 @@ fn calculate_bus_network(ctx: &mut EventCtx, ui: &UI) -> RoadColorer {
colorer.build(ctx, &ui.primary.map)
}
fn trip_stats(ui: &UI, ctx: &mut EventCtx) -> Plot<usize> {
let lines: Vec<(&str, Color, Option<TripMode>)> = vec![
(
"walking",
ui.cs.get("unzoomed pedestrian"),
Some(TripMode::Walk),
),
("biking", ui.cs.get("unzoomed bike"), Some(TripMode::Bike)),
(
"transit",
ui.cs.get("unzoomed bus"),
Some(TripMode::Transit),
),
("driving", ui.cs.get("unzoomed car"), Some(TripMode::Drive)),
("aborted", Color::PURPLE.alpha(0.5), None),
];
fn color_for_mode(m: TripMode, ui: &UI) -> Color {
match m {
TripMode::Walk => ui.cs.get("unzoomed pedestrian"),
TripMode::Bike => ui.cs.get("unzoomed bike"),
TripMode::Transit => ui.cs.get("unzoomed bus"),
TripMode::Drive => ui.cs.get("unzoomed car"),
}
}
fn trip_stats(ctx: &EventCtx, ui: &UI) -> Plot<usize> {
let mut lines: Vec<(String, Color, Option<TripMode>)> = TripMode::all()
.into_iter()
.map(|m| (m.to_string(), color_for_mode(m, ui), Some(m)))
.collect();
lines.push(("aborted".to_string(), Color::PURPLE.alpha(0.5), None));
// What times do we use for interpolation?
let num_x_pts = 100;
@ -419,8 +461,8 @@ fn trip_stats(ui: &UI, ctx: &mut EventCtx) -> Plot<usize> {
"finished trips",
lines
.into_iter()
.map(|(name, color, m)| Series {
label: name.to_string(),
.map(|(label, color, m)| Series {
label,
color,
pts: pts_per_mode.remove(&m).unwrap(),
})
@ -429,3 +471,55 @@ fn trip_stats(ui: &UI, ctx: &mut EventCtx) -> Plot<usize> {
ctx,
)
}
// TODO Refactor
pub fn calculate_road_thruput(r: RoadID, bucket: Duration, ctx: &EventCtx, ui: &UI) -> Plot<usize> {
Plot::new(
&format!(
"throughput of {} in {} buckets",
ui.primary.map.get_r(r).get_name(),
bucket.minimal_tostring()
),
ui.primary
.sim
.get_analytics()
.throughput_road(ui.primary.sim.time(), r, bucket)
.into_iter()
.map(|(m, pts)| Series {
label: m.to_string(),
color: color_for_mode(m, ui),
pts,
})
.collect(),
0,
ctx,
)
}
pub fn calculate_intersection_thruput(
i: IntersectionID,
bucket: Duration,
ctx: &EventCtx,
ui: &UI,
) -> Plot<usize> {
Plot::new(
&format!(
"throughput of {} in {} buckets",
i,
bucket.minimal_tostring()
),
ui.primary
.sim
.get_analytics()
.throughput_intersection(ui.primary.sim.time(), i, bucket)
.into_iter()
.map(|(m, pts)| Series {
label: m.to_string(),
color: color_for_mode(m, ui),
pts,
})
.collect(),
0,
ctx,
)
}

View File

@ -1,4 +1,4 @@
use crate::{CarID, Event, TripMode};
use crate::{AgentID, CarID, Event, TripMode, VehicleType};
use abstutil::Counter;
use derivative::Derivative;
use geom::{Duration, DurationHistogram};
@ -27,6 +27,9 @@ pub struct ThruputStats {
pub count_per_road: Counter<RoadID>,
#[serde(skip_serializing, skip_deserializing)]
pub count_per_intersection: Counter<IntersectionID>,
raw_per_road: Vec<(Duration, TripMode, RoadID)>,
raw_per_intersection: Vec<(Duration, TripMode, IntersectionID)>,
}
impl Analytics {
@ -35,6 +38,8 @@ impl Analytics {
thruput_stats: ThruputStats {
count_per_road: Counter::new(),
count_per_intersection: Counter::new(),
raw_per_road: Vec::new(),
raw_per_intersection: Vec::new(),
},
test_expectations: VecDeque::new(),
bus_arrivals: Vec::new(),
@ -44,11 +49,36 @@ impl Analytics {
}
pub fn event(&mut self, ev: Event, time: Duration, map: &Map) {
// TODO Plumb a flag
let raw_thruput = true;
// Throughput
if let Event::AgentEntersTraversable(_, to) = ev {
if let Event::AgentEntersTraversable(a, to) = ev {
let mode = match a {
AgentID::Pedestrian(_) => TripMode::Walk,
AgentID::Car(c) => match c.1 {
VehicleType::Car => TripMode::Drive,
VehicleType::Bike => TripMode::Bike,
VehicleType::Bus => TripMode::Transit,
},
};
match to {
Traversable::Lane(l) => self.thruput_stats.count_per_road.inc(map.get_l(l).parent),
Traversable::Turn(t) => self.thruput_stats.count_per_intersection.inc(t.parent),
Traversable::Lane(l) => {
let r = map.get_l(l).parent;
self.thruput_stats.count_per_road.inc(r);
if raw_thruput {
self.thruput_stats.raw_per_road.push((time, mode, r));
}
}
Traversable::Turn(t) => {
self.thruput_stats.count_per_intersection.inc(t.parent);
if raw_thruput {
self.thruput_stats
.raw_per_intersection
.push((time, mode, t.parent));
}
}
};
}
@ -173,4 +203,64 @@ impl Analytics {
}
delays_to_stop
}
// Slightly misleading -- TripMode::Transit means buses, not pedestrians taking transit
pub fn throughput_road(
&self,
now: Duration,
road: RoadID,
bucket: Duration,
) -> BTreeMap<TripMode, Vec<(Duration, usize)>> {
let mut max_this_bucket = now.min(bucket);
let mut per_mode = TripMode::all()
.into_iter()
.map(|m| (m, vec![(Duration::ZERO, 0), (max_this_bucket, 0)]))
.collect::<BTreeMap<_, _>>();
for (t, m, r) in &self.thruput_stats.raw_per_road {
if *r != road {
continue;
}
if *t > now {
break;
}
if *t > max_this_bucket {
max_this_bucket = now.min(max_this_bucket + bucket);
for vec in per_mode.values_mut() {
vec.push((max_this_bucket, 0));
}
}
per_mode.get_mut(m).unwrap().last_mut().unwrap().1 += 1;
}
per_mode
}
// TODO Refactor!
pub fn throughput_intersection(
&self,
now: Duration,
intersection: IntersectionID,
bucket: Duration,
) -> BTreeMap<TripMode, Vec<(Duration, usize)>> {
let mut per_mode = TripMode::all()
.into_iter()
.map(|m| (m, vec![(Duration::ZERO, 0)]))
.collect::<BTreeMap<_, _>>();
let mut max_this_bucket = bucket;
for (t, m, i) in &self.thruput_stats.raw_per_intersection {
if *i != intersection {
continue;
}
if *t > now {
break;
}
if *t > max_this_bucket {
max_this_bucket = now.min(max_this_bucket + bucket);
for vec in per_mode.values_mut() {
vec.push((max_this_bucket, 0));
}
}
per_mode.get_mut(m).unwrap().last_mut().unwrap().1 += 1;
}
per_mode
}
}