mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
start cleaing up thruput stats. remove the ability to change window size
This commit is contained in:
parent
7ff5e870d0
commit
a0a7cb1c4e
@ -13,7 +13,7 @@ regenerate the maps!
|
||||
5. Select what kind of on-street parking the road has
|
||||
6. Repeat
|
||||
7. Click **Generate OsmChange file**
|
||||
8. Upload the diff.osc file by adding a layer in JOSM
|
||||
8. Upload the diff.osc file by adding a layer in JOSM (or send it to me)
|
||||
|
||||
Like all edits to OSM, to figure out ground-truth, you can survey in-person or
|
||||
use
|
||||
@ -29,7 +29,7 @@ then use it to strengthen proposals for
|
||||
[pedestrianized streets](https://dabreegster.github.io/abstreet/lake_wash/proposal.html),
|
||||
[improving the bike network](https://www.glwstreets.org/45th-st-bridge-overview),
|
||||
and
|
||||
[mitigating the West Seattle bridge closure](https://www.westsideseattle.com/robinson-papers/2020/05/04/highland-park-action-coalition-calls-seattle-officials-traffic).
|
||||
[mitigating the West Seattle bridge closure](https://dabreegster.github.io/abstreet/west_seattle/proposal.html).
|
||||
A/B Street is only as good as its data, and parking is one of the biggest gaps.
|
||||
Missing data means unrealistic traffic as vehicles contend for few parking
|
||||
spots, and roads that look much wider than they are in reality.
|
||||
|
@ -51,9 +51,8 @@ pub fn traffic(
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.count_per_intersection
|
||||
.get(id)
|
||||
.intersection_thruput
|
||||
.total_for(id)
|
||||
)
|
||||
)));
|
||||
rows.push(txt.draw(ctx));
|
||||
@ -64,7 +63,7 @@ pub fn traffic(
|
||||
throughput(
|
||||
ctx,
|
||||
app,
|
||||
move |a, t| a.throughput_intersection(t, id, opts.bucket_size),
|
||||
move |a, t| a.throughput_intersection(t, id),
|
||||
opts.show_before,
|
||||
)
|
||||
.margin(10),
|
||||
@ -103,7 +102,7 @@ fn delay_plot(ctx: &EventCtx, app: &App, i: IntersectionID, opts: &DataOptions)
|
||||
.into_iter()
|
||||
.map(|stat| (stat, Vec::new()))
|
||||
.collect();
|
||||
for (t, distrib) in a.intersection_delays_bucketized(t, i, opts.bucket_size) {
|
||||
for (t, distrib) in a.intersection_delays_bucketized(t, i, Duration::hours(1)) {
|
||||
for (stat, pts) in series.iter_mut() {
|
||||
if distrib.count() == 0 {
|
||||
pts.push((t, Duration::ZERO));
|
||||
|
@ -146,14 +146,7 @@ pub fn traffic(
|
||||
let mut txt = Text::from(Line("Traffic over entire road, not just this lane"));
|
||||
txt.add(Line(format!(
|
||||
"Since midnight: {} agents crossed",
|
||||
prettyprint_usize(
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.count_per_road
|
||||
.get(r.id)
|
||||
)
|
||||
prettyprint_usize(app.primary.sim.get_analytics().road_thruput.total_for(r.id))
|
||||
)));
|
||||
rows.push(txt.draw(ctx));
|
||||
|
||||
@ -164,7 +157,7 @@ pub fn traffic(
|
||||
throughput(
|
||||
ctx,
|
||||
app,
|
||||
move |a, t| a.throughput_road(t, r, opts.bucket_size),
|
||||
move |a, t| a.throughput_road(t, r),
|
||||
opts.show_before,
|
||||
)
|
||||
.margin(10),
|
||||
|
@ -563,46 +563,26 @@ pub trait ContextualActions {
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct DataOptions {
|
||||
pub show_before: bool,
|
||||
pub bucket_size: Duration,
|
||||
}
|
||||
|
||||
impl DataOptions {
|
||||
pub fn new(app: &App) -> DataOptions {
|
||||
DataOptions {
|
||||
show_before: app.has_prebaked().is_some(),
|
||||
bucket_size: Duration::minutes(20),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_controls(&self, ctx: &mut EventCtx, app: &App) -> Widget {
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
"In".draw_text(ctx),
|
||||
Widget::dropdown(
|
||||
ctx,
|
||||
"bucket size",
|
||||
self.bucket_size,
|
||||
vec![
|
||||
Choice::new("20 minute", Duration::minutes(20)),
|
||||
Choice::new("1 hour", Duration::hours(1)),
|
||||
Choice::new("6 hour", Duration::hours(6)),
|
||||
],
|
||||
)
|
||||
.margin(3),
|
||||
"buckets".draw_text(ctx),
|
||||
]),
|
||||
if app.has_prebaked().is_some() {
|
||||
Checkbox::text(ctx, "Show before changes", None, self.show_before)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
])
|
||||
Widget::col(vec![if app.has_prebaked().is_some() {
|
||||
Checkbox::text(ctx, "Show before changes", None, self.show_before)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
}])
|
||||
}
|
||||
|
||||
pub fn from_controls(c: &Composite) -> DataOptions {
|
||||
DataOptions {
|
||||
show_before: c.has_widget("Show before changes") && c.is_checked("Show before changes"),
|
||||
bucket_size: c.dropdown_value("bucket size"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,14 +122,15 @@ pub fn throughput(ctx: &mut EventCtx, app: &App, compare: bool) -> Layers {
|
||||
vec!["0", "50", "90", "99", "100"],
|
||||
);
|
||||
|
||||
let stats = &app.primary.sim.get_analytics().thruput_stats;
|
||||
let stats = &app.primary.sim.get_analytics();
|
||||
|
||||
// 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 cnt = stats.road_thruput.all_total_counts();
|
||||
let roads = cnt.sorted_asc();
|
||||
let p50_idx = ((roads.len() as f64) * 0.5) as usize;
|
||||
let p90_idx = ((roads.len() as f64) * 0.9) as usize;
|
||||
let p99_idx = ((roads.len() as f64) * 0.99) as usize;
|
||||
@ -148,7 +149,8 @@ pub fn throughput(ctx: &mut EventCtx, app: &App, compare: bool) -> Layers {
|
||||
}
|
||||
// TODO dedupe
|
||||
{
|
||||
let intersections = stats.count_per_intersection.sorted_asc();
|
||||
let cnt = stats.intersection_thruput.all_total_counts();
|
||||
let intersections = cnt.sorted_asc();
|
||||
let p50_idx = ((intersections.len() as f64) * 0.5) as usize;
|
||||
let p90_idx = ((intersections.len() as f64) * 0.9) as usize;
|
||||
let p99_idx = ((intersections.len() as f64) * 0.99) as usize;
|
||||
@ -363,7 +365,6 @@ pub fn intersection_demand(ctx: &mut EventCtx, app: &App, i: IntersectionID) ->
|
||||
.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.thruput_stats
|
||||
.demand
|
||||
.get(&g.id)
|
||||
.cloned()
|
||||
|
@ -27,7 +27,7 @@ impl Time {
|
||||
}
|
||||
|
||||
// (hours, minutes, seconds, centiseconds)
|
||||
fn get_parts(self) -> (usize, usize, usize, usize) {
|
||||
pub fn get_parts(self) -> (usize, usize, usize, usize) {
|
||||
let mut remainder = self.0;
|
||||
let hours = (remainder / 3600.0).floor();
|
||||
remainder -= hours * 3600.0;
|
||||
|
@ -10,7 +10,13 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Analytics {
|
||||
// TODO rm this
|
||||
pub thruput_stats: ThruputStats,
|
||||
pub road_thruput: TimeSeriesCount<RoadID>,
|
||||
pub intersection_thruput: TimeSeriesCount<IntersectionID>,
|
||||
|
||||
// Unlike everything else in Analytics, this is just for a moment in time.
|
||||
pub demand: BTreeMap<TurnGroupID, usize>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub(crate) test_expectations: VecDeque<Event>,
|
||||
pub bus_arrivals: Vec<(Time, CarID, BusRouteID, BusStopID)>,
|
||||
@ -34,28 +40,20 @@ pub struct Analytics {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ThruputStats {
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub count_per_road: Counter<RoadID>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub count_per_intersection: Counter<IntersectionID>,
|
||||
|
||||
pub raw_per_road: Vec<(Time, TripMode, RoadID)>,
|
||||
pub 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 {
|
||||
pub fn new() -> Analytics {
|
||||
Analytics {
|
||||
thruput_stats: ThruputStats {
|
||||
count_per_road: Counter::new(),
|
||||
count_per_intersection: Counter::new(),
|
||||
raw_per_road: Vec::new(),
|
||||
raw_per_intersection: Vec::new(),
|
||||
demand: BTreeMap::new(),
|
||||
},
|
||||
road_thruput: TimeSeriesCount::new(),
|
||||
intersection_thruput: TimeSeriesCount::new(),
|
||||
demand: BTreeMap::new(),
|
||||
test_expectations: VecDeque::new(),
|
||||
bus_arrivals: Vec::new(),
|
||||
bus_passengers_waiting: Vec::new(),
|
||||
@ -74,30 +72,23 @@ impl Analytics {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Plumb a flag
|
||||
let raw_thruput = true;
|
||||
|
||||
// Throughput
|
||||
if let Event::AgentEntersTraversable(a, to) = ev {
|
||||
let mode = TripMode::from_agent(a);
|
||||
match to {
|
||||
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));
|
||||
}
|
||||
self.thruput_stats.raw_per_road.push((time, mode, r));
|
||||
self.road_thruput.record(time, r, mode);
|
||||
}
|
||||
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));
|
||||
}
|
||||
self.thruput_stats
|
||||
.raw_per_intersection
|
||||
.push((time, mode, t.parent));
|
||||
self.intersection_thruput.record(time, t.parent, mode);
|
||||
|
||||
if let Some(id) = map.get_turn_group(t) {
|
||||
*self.thruput_stats.demand.entry(id).or_insert(0) -= 1;
|
||||
*self.demand.entry(id).or_insert(0) -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -201,7 +192,7 @@ impl Analytics {
|
||||
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;
|
||||
*self.demand.entry(id).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -361,37 +352,29 @@ impl Analytics {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Slightly misleading -- TripMode::Transit means buses, not pedestrians taking transit
|
||||
pub fn throughput_road(
|
||||
&self,
|
||||
now: Time,
|
||||
road: RoadID,
|
||||
window_size: Duration,
|
||||
) -> BTreeMap<TripMode, Vec<(Time, usize)>> {
|
||||
self.throughput(now, road, window_size, &self.thruput_stats.raw_per_road)
|
||||
self.throughput(now, road, &self.thruput_stats.raw_per_road)
|
||||
}
|
||||
|
||||
pub fn throughput_intersection(
|
||||
&self,
|
||||
now: Time,
|
||||
intersection: IntersectionID,
|
||||
window_size: Duration,
|
||||
) -> BTreeMap<TripMode, Vec<(Time, usize)>> {
|
||||
self.throughput(
|
||||
now,
|
||||
intersection,
|
||||
window_size,
|
||||
&self.thruput_stats.raw_per_intersection,
|
||||
)
|
||||
self.throughput(now, intersection, &self.thruput_stats.raw_per_intersection)
|
||||
}
|
||||
|
||||
fn throughput<X: PartialEq>(
|
||||
&self,
|
||||
now: Time,
|
||||
obj: X,
|
||||
window_size: Duration,
|
||||
data: &Vec<(Time, TripMode, X)>,
|
||||
) -> BTreeMap<TripMode, Vec<(Time, usize)>> {
|
||||
let window_size = Duration::hours(1);
|
||||
let mut pts_per_mode: BTreeMap<TripMode, Vec<(Time, usize)>> = BTreeMap::new();
|
||||
let mut windows_per_mode: BTreeMap<TripMode, Window> = BTreeMap::new();
|
||||
for mode in TripMode::all() {
|
||||
@ -652,3 +635,46 @@ impl Window {
|
||||
self.times.len()
|
||||
}
|
||||
}
|
||||
|
||||
// Slightly misleading -- TripMode::Transit means buses, not pedestrians taking transit
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct TimeSeriesCount<X: Ord + Clone> {
|
||||
// (Road or intersection, mode, hour block) -> count for that hour
|
||||
counts: BTreeMap<(X, TripMode, usize), usize>,
|
||||
}
|
||||
|
||||
impl<X: Ord + Clone> TimeSeriesCount<X> {
|
||||
fn new() -> TimeSeriesCount<X> {
|
||||
TimeSeriesCount {
|
||||
counts: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn record(&mut self, time: Time, id: X, mode: TripMode) {
|
||||
let hour = time.get_parts().0;
|
||||
*self.counts.entry((id, mode, hour)).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
pub fn total_for(&self, id: X) -> usize {
|
||||
let mut cnt = 0;
|
||||
for mode in TripMode::all() {
|
||||
// TODO Hmm
|
||||
for hour in 0..24 {
|
||||
cnt += self
|
||||
.counts
|
||||
.get(&(id.clone(), mode, hour))
|
||||
.cloned()
|
||||
.unwrap_or(0);
|
||||
}
|
||||
}
|
||||
cnt
|
||||
}
|
||||
|
||||
pub fn all_total_counts(&self) -> Counter<X> {
|
||||
let mut cnt = Counter::new();
|
||||
for ((id, _, _), value) in &self.counts {
|
||||
cnt.add(id.clone(), *value);
|
||||
}
|
||||
cnt
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user