mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
experimenting with tracking and visualizing demand at traffic signals
This commit is contained in:
parent
b83e61eaee
commit
2afa2ef43f
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user