use crate::{AgentType, AlertLocation, CarID, Event, ParkingSpot, TripID, TripMode, TripPhaseType};
use abstutil::Counter;
use geom::{Distance, Duration, Time};
use map_model::{
BusRouteID, BusStopID, IntersectionID, LaneID, Map, ParkingLotID, Path, PathRequest, RoadID,
Traversable, TurnGroupID,
};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, VecDeque};
#[derive(Clone, Serialize, Deserialize)]
pub struct Analytics {
pub road_thruput: TimeSeriesCount<RoadID>,
pub intersection_thruput: TimeSeriesCount<IntersectionID>,
pub demand: BTreeMap<TurnGroupID, usize>,
pub bus_arrivals: Vec<(Time, CarID, BusRouteID, BusStopID)>,
pub passengers_boarding: BTreeMap<BusStopID, Vec<(Time, BusRouteID, Duration)>>,
pub passengers_alighting: BTreeMap<BusStopID, Vec<(Time, BusRouteID)>>,
pub started_trips: BTreeMap<TripID, Time>,
pub finished_trips: Vec<(Time, TripID, Option<TripMode>, Duration)>,
pub trip_log: Vec<(Time, TripID, Option<PathRequest>, TripPhaseType)>,
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration, AgentType)>>,
pub parking_lane_changes: BTreeMap<LaneID, Vec<(Time, bool)>>,
pub parking_lot_changes: BTreeMap<ParkingLotID, Vec<(Time, bool)>>,
pub(crate) alerts: Vec<(Time, AlertLocation, String)>,
record_anything: bool,
}
impl Analytics {
pub fn new() -> Analytics {
Analytics {
road_thruput: TimeSeriesCount::new(),
intersection_thruput: TimeSeriesCount::new(),
demand: BTreeMap::new(),
bus_arrivals: Vec::new(),
passengers_boarding: BTreeMap::new(),
passengers_alighting: BTreeMap::new(),
started_trips: BTreeMap::new(),
finished_trips: Vec::new(),
trip_log: Vec::new(),
intersection_delays: BTreeMap::new(),
parking_lane_changes: BTreeMap::new(),
parking_lot_changes: BTreeMap::new(),
alerts: Vec::new(),
record_anything: true,
}
}
pub fn event(&mut self, ev: Event, time: Time, map: &Map) {
if !self.record_anything {
return;
}
if let Event::AgentEntersTraversable(a, to, passengers) = ev {
match to {
Traversable::Lane(l) => {
self.road_thruput
.record(time, map.get_l(l).parent, a.to_type(), 1);
if let Some(n) = passengers {
self.road_thruput.record(
time,
map.get_l(l).parent,
AgentType::TransitRider,
n,
);
}
}
Traversable::Turn(t) => {
self.intersection_thruput
.record(time, t.parent, a.to_type(), 1);
if let Some(n) = passengers {
self.intersection_thruput.record(
time,
t.parent,
AgentType::TransitRider,
n,
);
}
if let Some(id) = map.get_turn_group(t) {
*self.demand.entry(id).or_insert(0) -= 1;
}
}
};
}
match ev {
Event::PersonLeavesMap(_, maybe_a, i, _) => {
if let Some(a) = maybe_a {
self.intersection_thruput.record(time, i, a.to_type(), 1);
}
}
Event::PersonEntersMap(_, a, i, _) => {
self.intersection_thruput.record(time, i, a.to_type(), 1);
}
_ => {}
}
if let Event::BusArrivedAtStop(bus, route, stop) = ev {
self.bus_arrivals.push((time, bus, route, stop));
}
if let Event::PassengerBoardsTransit(_, _, route, stop, waiting) = ev {
self.passengers_boarding
.entry(stop)
.or_insert_with(Vec::new)
.push((time, route, waiting));
}
if let Event::PassengerAlightsTransit(_, _, route, stop) = ev {
self.passengers_alighting
.entry(stop)
.or_insert_with(Vec::new)
.push((time, route));
}
if let Event::TripPhaseStarting(id, _, _, _) = ev {
self.started_trips.entry(id).or_insert(time);
}
if let Event::TripFinished {
trip,
mode,
total_time,
..
} = ev
{
self.finished_trips
.push((time, trip, Some(mode), total_time));
} else if let Event::TripAborted(id) = ev {
self.started_trips.entry(id).or_insert(time);
self.finished_trips.push((time, id, None, Duration::ZERO));
}
if let Event::IntersectionDelayMeasured(id, delay, agent) = ev {
self.intersection_delays
.entry(id)
.or_insert_with(Vec::new)
.push((time, delay, agent.to_type()));
}
if let Event::CarReachedParkingSpot(_, spot) = ev {
if let ParkingSpot::Onstreet(l, _) = spot {
self.parking_lane_changes
.entry(l)
.or_insert_with(Vec::new)
.push((time, true));
} else if let ParkingSpot::Lot(pl, _) = spot {
self.parking_lot_changes
.entry(pl)
.or_insert_with(Vec::new)
.push((time, true));
}
}
if let Event::CarLeftParkingSpot(_, spot) = ev {
if let ParkingSpot::Onstreet(l, _) = spot {
self.parking_lane_changes
.entry(l)
.or_insert_with(Vec::new)
.push((time, false));
} else if let ParkingSpot::Lot(pl, _) = spot {
self.parking_lot_changes
.entry(pl)
.or_insert_with(Vec::new)
.push((time, false));
}
}
match ev {
Event::TripPhaseStarting(id, _, maybe_req, phase_type) => {
self.trip_log.push((time, id, maybe_req, phase_type));
}
Event::TripAborted(id) => {
self.trip_log.push((time, id, None, TripPhaseType::Aborted));
}
Event::TripFinished { trip, .. } => {
self.trip_log
.push((time, trip, None, TripPhaseType::Finished));
}
Event::PathAmended(path) => {
self.record_demand(&path, map);
}
Event::Alert(loc, msg) => {
self.alerts.push((time, loc, msg));
}
_ => {}
}
}
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.demand.entry(id).or_insert(0) += 1;
}
}
}
}
pub fn finished_trip_time(&self, trip: TripID) -> Option<Duration> {
for (_, id, maybe_mode, dt) in &self.finished_trips {
if *id == trip {
if maybe_mode.is_some() {
return Some(*dt);
} else {
return None;
}
}
}
None
}
pub fn both_finished_trips(
&self,
now: Time,
before: &Analytics,
) -> Vec<(Duration, Duration, TripMode)> {
let mut a = BTreeMap::new();
for (t, id, maybe_mode, dt) in &self.finished_trips {
if *t > now {
break;
}
if maybe_mode.is_some() {
a.insert(*id, *dt);
}
}
let mut results = Vec::new();
for (t, id, maybe_mode, dt) in &before.finished_trips {
if *t > now {
break;
}
if let Some(mode) = maybe_mode {
if let Some(dt1) = a.remove(id) {
results.push((*dt, dt1, *mode));
}
}
}
results
}
pub fn compare_delay(&self, now: Time, before: &Analytics) -> Vec<(IntersectionID, Duration)> {
let mut results = Vec::new();
for (i, list1) in &self.intersection_delays {
if let Some(list2) = before.intersection_delays.get(i) {
let mut sum1 = Duration::ZERO;
for (t, dt, _) in list1 {
if *t > now {
break;
}
sum1 += *dt;
}
let mut sum2 = Duration::ZERO;
for (t, dt, _) in list2 {
if *t > now {
break;
}
sum2 += *dt;
}
if sum1 != sum2 {
results.push((*i, sum1 - sum2));
}
}
}
results
}
pub fn get_trip_phases(&self, trip: TripID, map: &Map) -> Vec<TripPhase> {
let mut phases: Vec<TripPhase> = Vec::new();
for (t, id, maybe_req, phase_type) in &self.trip_log {
if *id != trip {
continue;
}
if let Some(ref mut last) = phases.last_mut() {
last.end_time = Some(*t);
}
if *phase_type == TripPhaseType::Finished || *phase_type == TripPhaseType::Aborted {
break;
}
phases.push(TripPhase {
start_time: *t,
end_time: None,
path: maybe_req.as_ref().and_then(|req| {
map.pathfind(req.clone())
.map(|path| (req.start.dist_along(), path))
}),
has_path_req: maybe_req.is_some(),
phase_type: *phase_type,
})
}
phases
}
pub fn get_all_trip_phases(&self) -> BTreeMap<TripID, Vec<TripPhase>> {
let mut trips = BTreeMap::new();
for (t, id, maybe_req, phase_type) in &self.trip_log {
let phases: &mut Vec<TripPhase> = trips.entry(*id).or_insert_with(Vec::new);
if let Some(ref mut last) = phases.last_mut() {
last.end_time = Some(*t);
}
if *phase_type == TripPhaseType::Finished {
continue;
}
if *phase_type == TripPhaseType::Aborted {
trips.remove(id);
continue;
}
phases.push(TripPhase {
start_time: *t,
end_time: None,
path: None,
has_path_req: maybe_req.is_some(),
phase_type: *phase_type,
})
}
trips
}
pub fn active_agents(&self, now: Time) -> Vec<(Time, usize)> {
let mut starts_stops: Vec<(Time, bool)> = Vec::new();
for t in self.started_trips.values() {
if *t <= now {
starts_stops.push((*t, false));
}
}
for (t, _, _, _) in &self.finished_trips {
if *t > now {
break;
}
starts_stops.push((*t, true));
}
starts_stops.sort();
let mut pts = Vec::new();
let mut cnt = 0;
let mut last_t = Time::START_OF_DAY;
for (t, ended) in starts_stops {
if t != last_t {
pts.push((last_t, cnt));
}
last_t = t;
if ended {
if cnt == 0 {
panic!("active_agents at {} has more ended trips than started", t);
}
cnt -= 1;
} else {
cnt += 1;
}
}
pts.push((last_t, cnt));
if last_t != now {
pts.push((now, cnt));
}
pts
}
pub fn parking_lane_availability(
&self,
now: Time,
l: LaneID,
capacity: usize,
) -> Vec<(Time, usize)> {
if let Some(changes) = self.parking_lane_changes.get(&l) {
Analytics::parking_spot_availability(now, changes, capacity)
} else {
vec![(Time::START_OF_DAY, capacity), (now, capacity)]
}
}
pub fn parking_lot_availability(
&self,
now: Time,
pl: ParkingLotID,
capacity: usize,
) -> Vec<(Time, usize)> {
if let Some(changes) = self.parking_lot_changes.get(&pl) {
Analytics::parking_spot_availability(now, changes, capacity)
} else {
vec![(Time::START_OF_DAY, capacity), (now, capacity)]
}
}
fn parking_spot_availability(
now: Time,
changes: &Vec<(Time, bool)>,
capacity: usize,
) -> Vec<(Time, usize)> {
let mut pts = Vec::new();
let mut cnt = capacity;
let mut last_t = Time::START_OF_DAY;
for (t, filled) in changes {
if *t > now {
break;
}
if *t != last_t {
pts.push((last_t, cnt));
}
last_t = *t;
if *filled {
if cnt == 0 {
panic!("parking_spot_availability at {} went below 0", t);
}
cnt -= 1;
} else {
cnt += 1;
}
}
pts.push((last_t, cnt));
if last_t != now {
pts.push((now, cnt));
}
pts
}
}
impl Default for Analytics {
fn default() -> Analytics {
let mut a = Analytics::new();
a.record_anything = false;
a
}
}
#[derive(Debug)]
pub struct TripPhase {
pub start_time: Time,
pub end_time: Option<Time>,
pub path: Option<(Distance, Path)>,
pub has_path_req: bool,
pub phase_type: TripPhaseType,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct TimeSeriesCount<X: Ord + Clone> {
pub counts: BTreeMap<(X, AgentType, usize), usize>,
pub raw: Vec<(Time, AgentType, X)>,
}
impl<X: Ord + Clone> TimeSeriesCount<X> {
fn new() -> TimeSeriesCount<X> {
TimeSeriesCount {
counts: BTreeMap::new(),
raw: Vec::new(),
}
}
fn record(&mut self, time: Time, id: X, agent_type: AgentType, count: usize) {
if false {
for _ in 0..count {
self.raw.push((time, agent_type, id.clone()));
}
}
let hour = time.get_parts().0;
*self.counts.entry((id, agent_type, hour)).or_insert(0) += count;
}
pub fn total_for(&self, id: X) -> usize {
let mut cnt = 0;
for agent_type in AgentType::all() {
for hour in 0..24 {
cnt += self
.counts
.get(&(id.clone(), agent_type, 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
}
pub fn count_per_hour(&self, id: X, time: Time) -> Vec<(AgentType, Vec<(Time, usize)>)> {
let hour = time.get_hours();
let mut results = Vec::new();
for agent_type in AgentType::all() {
let mut pts = Vec::new();
for hour in 0..=hour {
let cnt = self
.counts
.get(&(id.clone(), agent_type, hour))
.cloned()
.unwrap_or(0);
pts.push((Time::START_OF_DAY + Duration::hours(hour), cnt));
pts.push((Time::START_OF_DAY + Duration::hours(hour + 1), cnt));
}
pts.pop();
results.push((agent_type, pts));
}
results
}
pub fn raw_throughput(&self, now: Time, id: X) -> Vec<(AgentType, Vec<(Time, usize)>)> {
let window_size = Duration::hours(1);
let mut pts_per_type: BTreeMap<AgentType, Vec<(Time, usize)>> = BTreeMap::new();
let mut windows_per_type: BTreeMap<AgentType, Window> = BTreeMap::new();
for agent_type in AgentType::all() {
pts_per_type.insert(agent_type, vec![(Time::START_OF_DAY, 0)]);
windows_per_type.insert(agent_type, Window::new(window_size));
}
for (t, agent_type, x) in &self.raw {
if *x != id {
continue;
}
if *t > now {
break;
}
let count = windows_per_type.get_mut(agent_type).unwrap().add(*t);
pts_per_type.get_mut(agent_type).unwrap().push((*t, count));
}
for (agent_type, pts) in pts_per_type.iter_mut() {
let mut window = windows_per_type.remove(agent_type).unwrap();
let t = (pts.last().unwrap().0 + window_size + Duration::seconds(0.1)).min(now);
if pts.last().unwrap().0 != t {
pts.push((t, window.count(t)));
}
if pts.last().unwrap().0 != now {
pts.push((now, window.count(now)));
}
}
pts_per_type.into_iter().collect()
}
}
pub struct Window {
times: VecDeque<Time>,
window_size: Duration,
}
impl Window {
pub fn new(window_size: Duration) -> Window {
Window {
times: VecDeque::new(),
window_size,
}
}
pub fn add(&mut self, time: Time) -> usize {
self.times.push_back(time);
self.count(time)
}
pub fn count(&mut self, end: Time) -> usize {
while !self.times.is_empty() && end - *self.times.front().unwrap() > self.window_size {
self.times.pop_front();
}
self.times.len()
}
}