1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2use std::fmt::Write;
3
4use serde::{Deserialize, Serialize};
5
6use abstutil::Counter;
7use geom::{Duration, Pt2D, Time};
8use map_model::{
9 CompressedMovementID, IntersectionID, LaneID, Map, MovementID, ParkingLotID, Path, PathRequest,
10 RoadID, TransitRouteID, TransitStopID, Traversable, TurnID,
11};
12use synthpop::TripMode;
13
14use crate::{AgentID, AgentType, AlertLocation, CarID, Event, ParkingSpot, TripID, TripPhaseType};
15
16#[derive(Clone, Serialize, Deserialize)]
25pub struct Analytics {
26 pub road_thruput: TimeSeriesCount<RoadID>,
27 pub intersection_thruput: TimeSeriesCount<IntersectionID>,
28 pub traffic_signal_thruput: TimeSeriesCount<CompressedMovementID>,
32
33 pub demand: BTreeMap<MovementID, usize>,
36
37 pub bus_arrivals: Vec<(Time, CarID, TransitRouteID, TransitStopID)>,
39 pub passengers_boarding: BTreeMap<TransitStopID, Vec<(Time, TransitRouteID, Duration)>>,
41 pub passengers_alighting: BTreeMap<TransitStopID, Vec<(Time, TransitRouteID)>>,
42
43 pub started_trips: BTreeMap<TripID, Time>,
44 pub finished_trips: Vec<(Time, TripID, TripMode, Option<Duration>)>,
46
47 pub problems_per_trip: BTreeMap<TripID, Vec<(Time, Problem)>>,
49
50 pub trip_log: Vec<(Time, TripID, Option<PathRequest>, TripPhaseType)>,
52
53 pub intersection_delays: BTreeMap<IntersectionID, Vec<(u8, Time, Duration, AgentType)>>,
56
57 pub parking_lane_changes: BTreeMap<LaneID, Vec<(Time, bool)>>,
59 pub parking_lot_changes: BTreeMap<ParkingLotID, Vec<(Time, bool)>>,
60
61 pub(crate) alerts: Vec<(Time, AlertLocation, String)>,
62
63 record_anything: bool,
65}
66
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68pub enum Problem {
69 IntersectionDelay(IntersectionID, Duration),
71 ComplexIntersectionCrossing(IntersectionID),
73 ArterialIntersectionCrossing(TurnID),
75 OvertakeDesired(Traversable),
77 PedestrianOvercrowding(Traversable),
79}
80
81impl Problem {
82 pub fn point(&self, map: &Map) -> Pt2D {
85 match self {
86 Problem::IntersectionDelay(i, _) | Problem::ComplexIntersectionCrossing(i) => {
87 map.get_i(*i).polygon.center()
88 }
89 Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
90 on.get_polyline(map).middle()
91 }
92 Problem::ArterialIntersectionCrossing(t) => map.get_t(*t).geom.middle(),
93 }
94 }
95}
96
97#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
98pub enum ProblemType {
99 IntersectionDelay,
100 ComplexIntersectionCrossing,
101 OvertakeDesired,
102 ArterialIntersectionCrossing,
103 PedestrianOvercrowding,
104}
105
106impl From<&Problem> for ProblemType {
107 fn from(problem: &Problem) -> Self {
108 match problem {
109 Problem::IntersectionDelay(_, _) => Self::IntersectionDelay,
110 Problem::ComplexIntersectionCrossing(_) => Self::ComplexIntersectionCrossing,
111 Problem::OvertakeDesired(_) => Self::OvertakeDesired,
112 Problem::ArterialIntersectionCrossing(_) => Self::ArterialIntersectionCrossing,
113 Problem::PedestrianOvercrowding(_) => Self::PedestrianOvercrowding,
114 }
115 }
116}
117
118impl ProblemType {
119 pub fn count(self, problems: &[(Time, Problem)]) -> usize {
120 let mut cnt = 0;
121 for (_, problem) in problems {
122 if self == ProblemType::from(problem) {
123 cnt += 1;
124 }
125 }
126 cnt
127 }
128
129 pub fn all() -> Vec<ProblemType> {
130 vec![
131 ProblemType::IntersectionDelay,
132 ProblemType::ComplexIntersectionCrossing,
133 ProblemType::OvertakeDesired,
134 ProblemType::ArterialIntersectionCrossing,
135 ProblemType::PedestrianOvercrowding,
136 ]
137 }
138
139 pub fn name(self) -> &'static str {
140 match self {
141 ProblemType::IntersectionDelay => "delays",
142 ProblemType::ComplexIntersectionCrossing => {
143 "where cyclists cross complex intersections"
144 }
145 ProblemType::OvertakeDesired => "where cars want to overtake cyclists",
146 ProblemType::ArterialIntersectionCrossing => {
147 "where pedestrians cross arterial intersections"
148 }
149 ProblemType::PedestrianOvercrowding => "where pedestrians are over-crowded",
150 }
151 }
152}
153
154impl Analytics {
155 pub fn new(record_anything: bool) -> Analytics {
156 Analytics {
157 road_thruput: TimeSeriesCount::new(),
158 intersection_thruput: TimeSeriesCount::new(),
159 traffic_signal_thruput: TimeSeriesCount::new(),
160 demand: BTreeMap::new(),
161 bus_arrivals: Vec::new(),
162 passengers_boarding: BTreeMap::new(),
163 passengers_alighting: BTreeMap::new(),
164 started_trips: BTreeMap::new(),
165 finished_trips: Vec::new(),
166 problems_per_trip: BTreeMap::new(),
167 trip_log: Vec::new(),
168 intersection_delays: BTreeMap::new(),
169 parking_lane_changes: BTreeMap::new(),
170 parking_lot_changes: BTreeMap::new(),
171 alerts: Vec::new(),
172 record_anything,
173 }
174 }
175
176 pub fn event(&mut self, ev: Event, time: Time, map: &Map) {
177 if !self.record_anything {
178 return;
179 }
180
181 if let Event::AgentEntersTraversable(a, _, to, passengers) = ev {
183 match to {
184 Traversable::Lane(l) => {
185 self.road_thruput.record(time, l.road, a.to_type(), 1);
186 if let Some(n) = passengers {
187 self.road_thruput
188 .record(time, l.road, AgentType::TransitRider, n);
189 }
190 }
191 Traversable::Turn(t) => {
192 self.intersection_thruput
193 .record(time, t.parent, a.to_type(), 1);
194 if let Some(n) = passengers {
195 self.intersection_thruput.record(
196 time,
197 t.parent,
198 AgentType::TransitRider,
199 n,
200 );
201 }
202
203 if let Some((id, compressed)) = map.get_movement_for_traffic_signal(t) {
204 *self.demand.entry(id).or_insert(0) -= 1;
205 self.traffic_signal_thruput
206 .record(time, compressed, a.to_type(), 1);
207 if let Some(n) = passengers {
208 self.traffic_signal_thruput.record(
209 time,
210 compressed,
211 AgentType::TransitRider,
212 n,
213 );
214 }
215 }
216 }
217 };
218 }
219 match ev {
220 Event::PersonLeavesMap(_, Some(a), i) => {
221 self.intersection_thruput.record(time, i, a.to_type(), 1);
223 }
224 Event::PersonEntersMap(_, a, i) => {
225 self.intersection_thruput.record(time, i, a.to_type(), 1);
226 }
227 _ => {}
228 }
229
230 if let Event::BusArrivedAtStop(bus, route, stop) = ev {
232 self.bus_arrivals.push((time, bus, route, stop));
233 }
234
235 if let Event::PassengerBoardsTransit(_, _, route, stop, waiting) = ev {
237 self.passengers_boarding
238 .entry(stop)
239 .or_insert_with(Vec::new)
240 .push((time, route, waiting));
241 }
242 if let Event::PassengerAlightsTransit(_, _, route, stop) = ev {
243 self.passengers_alighting
244 .entry(stop)
245 .or_insert_with(Vec::new)
246 .push((time, route));
247 }
248
249 if let Event::TripPhaseStarting(id, _, _, _) = ev {
251 self.started_trips.entry(id).or_insert(time);
252 }
253
254 if let Event::TripFinished {
256 trip,
257 mode,
258 total_time,
259 ..
260 } = ev
261 {
262 self.finished_trips
263 .push((time, trip, mode, Some(total_time)));
264 } else if let Event::TripCancelled(id, mode) = ev {
265 self.started_trips.entry(id).or_insert(time);
266 self.finished_trips.push((time, id, mode, None));
267 }
268
269 if let Event::IntersectionDelayMeasured(trip_id, turn_id, agent, delay) = ev {
271 let threshold = match agent {
272 AgentID::Car(_) => Duration::seconds(30.0),
273 AgentID::Pedestrian(_) => Duration::seconds(15.0),
274 AgentID::BusPassenger(_, _) => Duration::hours(24),
276 };
277 if delay > threshold {
278 self.problems_per_trip
279 .entry(trip_id)
280 .or_insert_with(Vec::new)
281 .push((time, Problem::IntersectionDelay(turn_id.parent, delay)));
282 }
283
284 if let Some((_, compressed)) = map.get_movement_for_traffic_signal(turn_id) {
287 self.intersection_delays
288 .entry(turn_id.parent)
289 .or_insert_with(Vec::new)
290 .push((compressed.idx, time, delay, agent.to_type()));
291 }
292 }
293
294 if let Event::CarReachedParkingSpot(_, spot) = ev {
296 if let ParkingSpot::Onstreet(l, _) = spot {
297 self.parking_lane_changes
298 .entry(l)
299 .or_insert_with(Vec::new)
300 .push((time, true));
301 } else if let ParkingSpot::Lot(pl, _) = spot {
302 self.parking_lot_changes
303 .entry(pl)
304 .or_insert_with(Vec::new)
305 .push((time, true));
306 }
307 }
308 if let Event::CarLeftParkingSpot(_, spot) = ev {
309 if let ParkingSpot::Onstreet(l, _) = spot {
310 self.parking_lane_changes
311 .entry(l)
312 .or_insert_with(Vec::new)
313 .push((time, false));
314 } else if let ParkingSpot::Lot(pl, _) = spot {
315 self.parking_lot_changes
316 .entry(pl)
317 .or_insert_with(Vec::new)
318 .push((time, false));
319 }
320 }
321
322 if let Event::AgentEntersTraversable(a, Some(trip), Traversable::Turn(t), _) = ev {
324 if a.to_type() == AgentType::Bike && map.get_i(t.parent).roads.len() > 4 {
325 self.problems_per_trip
329 .entry(trip)
330 .or_insert_with(Vec::new)
331 .push((time, Problem::ComplexIntersectionCrossing(t.parent)));
332 }
333 }
334
335 if let Event::AgentEntersTraversable(a, Some(trip), Traversable::Turn(t), _) = ev {
336 let turn = map.get_t(t);
337 if a.to_type() == AgentType::Pedestrian && turn.is_crossing_arterial_intersection(map) {
338 self.problems_per_trip
339 .entry(trip)
340 .or_insert_with(Vec::new)
341 .push((time, Problem::ArterialIntersectionCrossing(turn.id)));
342 }
343 }
344
345 match ev {
347 Event::TripPhaseStarting(id, _, maybe_req, phase_type) => {
348 self.trip_log.push((time, id, maybe_req, phase_type));
349 }
350 Event::TripCancelled(id, _) => {
351 self.trip_log
352 .push((time, id, None, TripPhaseType::Cancelled));
353 }
354 Event::TripFinished { trip, .. } => {
355 self.trip_log
356 .push((time, trip, None, TripPhaseType::Finished));
357 }
358 Event::PathAmended(path) => {
359 self.record_demand(&path, map);
360 }
361 Event::Alert(loc, msg) => {
362 self.alerts.push((time, loc, msg));
363 }
364 Event::ProblemEncountered(trip, problem) => {
365 self.problems_per_trip
366 .entry(trip)
367 .or_insert_with(Vec::new)
368 .push((time, problem));
369 }
370 _ => {}
371 }
372 }
373
374 pub fn record_demand(&mut self, path: &Path, map: &Map) {
375 for step in path.get_steps() {
376 if let Traversable::Turn(t) = step.as_traversable() {
377 if let Some((id, _)) = map.get_movement_for_traffic_signal(t) {
378 *self.demand.entry(id).or_insert(0) += 1;
379 }
380 }
381 }
382 }
383
384 pub fn finished_trip_time(&self, trip: TripID) -> Option<Duration> {
389 for (_, id, _, maybe_dt) in &self.finished_trips {
391 if *id == trip {
392 return *maybe_dt;
393 }
394 }
395 None
396 }
397
398 pub fn both_finished_trips(
400 &self,
401 now: Time,
402 before: &Analytics,
403 ) -> Vec<(TripID, Duration, Duration, TripMode)> {
404 let mut a = BTreeMap::new();
405 for (t, id, _, maybe_dt) in &self.finished_trips {
406 if *t > now {
407 break;
408 }
409 if let Some(dt) = maybe_dt {
410 a.insert(*id, *dt);
411 }
412 }
413
414 let mut results = Vec::new();
415 for (t, id, mode, maybe_dt) in &before.finished_trips {
416 if *t > now {
417 break;
418 }
419 if let Some(dt) = maybe_dt {
420 if let Some(dt1) = a.remove(id) {
421 results.push((*id, *dt, dt1, *mode));
422 }
423 }
424 }
425 results
426 }
427
428 pub fn get_trip_phases(&self, trip: TripID, map: &Map) -> Vec<TripPhase> {
431 let mut phases: Vec<TripPhase> = Vec::new();
432 for (t, id, maybe_req, phase_type) in &self.trip_log {
433 if *id != trip {
434 continue;
435 }
436 if let Some(ref mut last) = phases.last_mut() {
437 last.end_time = Some(*t);
438 }
439 if *phase_type == TripPhaseType::Finished || *phase_type == TripPhaseType::Cancelled {
440 break;
441 }
442 phases.push(TripPhase {
443 start_time: *t,
444 end_time: None,
445 path: maybe_req.clone().and_then(|req| map.pathfind(req).ok()),
446 has_path_req: maybe_req.is_some(),
447 phase_type: *phase_type,
448 })
449 }
450 phases
451 }
452
453 pub fn get_all_trip_phases(&self) -> BTreeMap<TripID, Vec<TripPhase>> {
454 let mut trips = BTreeMap::new();
455 for (t, id, maybe_req, phase_type) in &self.trip_log {
456 let phases: &mut Vec<TripPhase> = trips.entry(*id).or_insert_with(Vec::new);
457 if let Some(ref mut last) = phases.last_mut() {
458 last.end_time = Some(*t);
459 }
460 if *phase_type == TripPhaseType::Finished {
461 continue;
462 }
463 if *phase_type == TripPhaseType::Cancelled {
465 trips.remove(id);
466 continue;
467 }
468 phases.push(TripPhase {
469 start_time: *t,
470 end_time: None,
471 path: None,
473 has_path_req: maybe_req.is_some(),
474 phase_type: *phase_type,
475 })
476 }
477 trips
478 }
479
480 pub fn active_agents(&self, now: Time) -> Vec<(Time, usize)> {
481 let mut starts_stops: Vec<(Time, bool)> = Vec::new();
482 for t in self.started_trips.values() {
483 if *t <= now {
484 starts_stops.push((*t, false));
485 }
486 }
487 for (t, _, _, _) in &self.finished_trips {
488 if *t > now {
489 break;
490 }
491 starts_stops.push((*t, true));
492 }
493 starts_stops.sort();
495
496 let mut pts = Vec::new();
497 let mut cnt = 0;
498 let mut last_t = Time::START_OF_DAY;
499 for (t, ended) in starts_stops {
500 if t != last_t {
501 pts.push((last_t, cnt));
503 }
504 last_t = t;
505 if ended {
506 if cnt == 0 {
508 panic!("active_agents at {} has more ended trips than started", t);
509 }
510 cnt -= 1;
511 } else {
512 cnt += 1;
513 }
514 }
515 pts.push((last_t, cnt));
516 if last_t != now {
517 pts.push((now, cnt));
518 }
519 pts
520 }
521
522 pub fn parking_lane_availability(
524 &self,
525 now: Time,
526 l: LaneID,
527 capacity: usize,
528 ) -> Vec<(Time, usize)> {
529 if let Some(changes) = self.parking_lane_changes.get(&l) {
530 Analytics::parking_spot_availability(now, changes, capacity)
531 } else {
532 vec![(Time::START_OF_DAY, capacity), (now, capacity)]
533 }
534 }
535 pub fn parking_lot_availability(
536 &self,
537 now: Time,
538 pl: ParkingLotID,
539 capacity: usize,
540 ) -> Vec<(Time, usize)> {
541 if let Some(changes) = self.parking_lot_changes.get(&pl) {
542 Analytics::parking_spot_availability(now, changes, capacity)
543 } else {
544 vec![(Time::START_OF_DAY, capacity), (now, capacity)]
545 }
546 }
547
548 fn parking_spot_availability(
549 now: Time,
550 changes: &[(Time, bool)],
551 capacity: usize,
552 ) -> Vec<(Time, usize)> {
553 let mut pts = Vec::new();
554 let mut cnt = capacity;
555 let mut last_t = Time::START_OF_DAY;
556
557 for (t, filled) in changes {
558 if *t > now {
559 break;
560 }
561 if *t != last_t {
562 pts.push((last_t, cnt));
564 }
565 last_t = *t;
566 if *filled {
567 if cnt == 0 {
568 panic!("parking_spot_availability at {} went below 0", t);
569 }
570 cnt -= 1;
571 } else {
572 cnt += 1;
573 }
574 }
575 pts.push((last_t, cnt));
576 if last_t != now {
577 pts.push((now, cnt));
578 }
579 pts
580 }
581
582 pub fn problems_per_intersection(
583 &self,
584 now: Time,
585 id: IntersectionID,
586 ) -> Vec<(ProblemType, Vec<(Time, usize)>)> {
587 let window_size = Duration::minutes(15);
588
589 let mut raw_per_type: BTreeMap<ProblemType, Vec<Time>> = BTreeMap::new();
590 for problem_type in ProblemType::all() {
591 raw_per_type.insert(problem_type, Vec::new());
592 }
593
594 for (_, problems) in &self.problems_per_trip {
595 for (time, problem) in problems {
596 if *time > now {
597 break;
598 }
599 let i = match problem {
600 Problem::IntersectionDelay(i, _) | Problem::ComplexIntersectionCrossing(i) => {
601 *i
602 }
603 Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
604 match on {
605 Traversable::Turn(t) => t.parent,
606 _ => {
607 continue;
608 }
609 }
610 }
611 Problem::ArterialIntersectionCrossing(t) => t.parent,
612 };
613 if id == i {
614 raw_per_type
615 .get_mut(&ProblemType::from(problem))
616 .unwrap()
617 .push(*time);
618 }
619 }
620 }
621
622 let mut result = Vec::new();
623 for (problem_type, mut raw) in raw_per_type {
624 raw.sort();
625 let mut pts = vec![(Time::START_OF_DAY, 0)];
626 let mut window = SlidingWindow::new(window_size);
627 for t in raw {
628 let count = window.add(t);
629 pts.push((t, count));
630 }
631 window.close_off_pts(&mut pts, now);
632 result.push((problem_type, pts));
633 }
634 result
635 }
636
637 pub fn problems_per_lane(
638 &self,
639 now: Time,
640 id: LaneID,
641 ) -> Vec<(ProblemType, Vec<(Time, usize)>)> {
642 let window_size = Duration::minutes(15);
643
644 let mut raw_per_type: BTreeMap<ProblemType, Vec<Time>> = BTreeMap::new();
645 for problem_type in ProblemType::all() {
646 raw_per_type.insert(problem_type, Vec::new());
647 }
648
649 for (_, problems) in &self.problems_per_trip {
650 for (time, problem) in problems {
651 if *time > now {
652 break;
653 }
654 let l = match problem {
655 Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
656 match on {
657 Traversable::Lane(l) => *l,
658 _ => {
659 continue;
660 }
661 }
662 }
663 _ => {
664 continue;
665 }
666 };
667 if id == l {
668 raw_per_type
669 .get_mut(&ProblemType::from(problem))
670 .unwrap()
671 .push(*time);
672 }
673 }
674 }
675
676 let mut result = Vec::new();
677 for (problem_type, mut raw) in raw_per_type {
678 raw.sort();
679 let mut pts = vec![(Time::START_OF_DAY, 0)];
680 let mut window = SlidingWindow::new(window_size);
681 for t in raw {
682 let count = window.add(t);
683 pts.push((t, count));
684 }
685 window.close_off_pts(&mut pts, now);
686 result.push((problem_type, pts));
687 }
688 result
689 }
690}
691
692impl Default for Analytics {
693 fn default() -> Analytics {
694 Analytics::new(false)
695 }
696}
697
698#[derive(Debug)]
699pub struct TripPhase {
700 pub start_time: Time,
701 pub end_time: Option<Time>,
702 pub path: Option<Path>,
703 pub has_path_req: bool,
704 pub phase_type: TripPhaseType,
705}
706
707#[derive(Clone, Serialize, Deserialize)]
709pub struct TimeSeriesCount<X: Ord + Clone> {
710 pub counts: BTreeMap<(X, AgentType, usize), usize>,
712
713 pub raw: Vec<(Time, AgentType, X)>,
716}
717
718impl<X: Ord + Clone> TimeSeriesCount<X> {
719 fn new() -> TimeSeriesCount<X> {
720 TimeSeriesCount {
721 counts: BTreeMap::new(),
722 raw: Vec::new(),
723 }
724 }
725
726 fn record(&mut self, time: Time, id: X, agent_type: AgentType, count: usize) {
727 if false {
729 for _ in 0..count {
732 self.raw.push((time, agent_type, id.clone()));
733 }
734 }
735
736 *self
737 .counts
738 .entry((id, agent_type, time.get_hours()))
739 .or_insert(0) += count;
740 }
741
742 pub fn total_for(&self, id: X) -> usize {
743 self.total_for_with_agent_types(id, AgentType::all().into_iter().collect())
744 }
745
746 pub fn total_for_with_agent_types(&self, id: X, agent_types: BTreeSet<AgentType>) -> usize {
747 let mut cnt = 0;
748 for agent_type in agent_types {
749 for hour in 0..24 {
751 cnt += self
752 .counts
753 .get(&(id.clone(), agent_type, hour))
754 .cloned()
755 .unwrap_or(0);
756 }
757 }
758 cnt
759 }
760
761 pub fn total_for_by_time(&self, id: X, now: Time) -> usize {
762 let mut cnt = 0;
763 for agent_type in AgentType::all() {
764 for hour in 0..=now.get_hours() {
765 cnt += self
766 .counts
767 .get(&(id.clone(), agent_type, hour))
768 .cloned()
769 .unwrap_or(0);
770 }
771 }
772 cnt
773 }
774
775 pub fn all_total_counts(&self, agent_types: &BTreeSet<AgentType>) -> Counter<X> {
776 let mut cnt = Counter::new();
777 for ((id, agent_type, _), value) in &self.counts {
778 if agent_types.contains(agent_type) {
779 cnt.add(id.clone(), *value);
780 }
781 }
782 cnt
783 }
784
785 pub fn count_per_hour(&self, id: X, time: Time) -> Vec<(AgentType, Vec<(Time, usize)>)> {
786 let hour = time.get_hours();
787 let mut results = Vec::new();
788 for agent_type in AgentType::all() {
789 let mut pts = Vec::new();
790 for hour in 0..=hour {
791 let cnt = self
792 .counts
793 .get(&(id.clone(), agent_type, hour))
794 .cloned()
795 .unwrap_or(0);
796 pts.push((Time::START_OF_DAY + Duration::hours(hour), cnt));
797 pts.push((Time::START_OF_DAY + Duration::hours(hour + 1), cnt));
798 }
799 pts.pop();
800 results.push((agent_type, pts));
801 }
802 results
803 }
804
805 pub fn raw_throughput(&self, now: Time, id: X) -> Vec<(AgentType, Vec<(Time, usize)>)> {
806 let window_size = Duration::hours(1);
807 let mut pts_per_type: BTreeMap<AgentType, Vec<(Time, usize)>> = BTreeMap::new();
808 let mut windows_per_type: BTreeMap<AgentType, SlidingWindow> = BTreeMap::new();
809 for agent_type in AgentType::all() {
810 pts_per_type.insert(agent_type, vec![(Time::START_OF_DAY, 0)]);
811 windows_per_type.insert(agent_type, SlidingWindow::new(window_size));
812 }
813
814 for (t, agent_type, x) in &self.raw {
815 if *x != id {
816 continue;
817 }
818 if *t > now {
819 break;
820 }
821
822 let count = windows_per_type.get_mut(agent_type).unwrap().add(*t);
823 pts_per_type.get_mut(agent_type).unwrap().push((*t, count));
824 }
825
826 for (agent_type, pts) in pts_per_type.iter_mut() {
827 let mut window = windows_per_type.remove(agent_type).unwrap();
828
829 window.close_off_pts(pts, now);
830 }
831
832 pts_per_type.into_iter().collect()
833 }
834
835 pub fn export_csv<F: Fn(&X) -> usize>(&self, extract_id: F) -> String {
837 let mut out = String::new();
838 writeln!(out, "id,agent_type,hour,count").unwrap();
839 for ((id, agent_type, hour), count) in &self.counts {
840 writeln!(
841 out,
842 "{},{:?},{},{}",
843 extract_id(id),
844 agent_type,
845 hour,
846 count
847 )
848 .unwrap();
849 }
850 out
851 }
852}
853
854pub struct SlidingWindow {
856 times: VecDeque<Time>,
857 window_size: Duration,
858}
859
860impl SlidingWindow {
861 pub fn new(window_size: Duration) -> SlidingWindow {
862 SlidingWindow {
863 times: VecDeque::new(),
864 window_size,
865 }
866 }
867
868 pub fn add(&mut self, time: Time) -> usize {
870 self.times.push_back(time);
871 self.count(time)
872 }
873
874 pub fn count(&mut self, end: Time) -> usize {
876 while !self.times.is_empty() && end - *self.times.front().unwrap() > self.window_size {
877 self.times.pop_front();
878 }
879 self.times.len()
880 }
881
882 pub fn close_off_pts(&mut self, pts: &mut Vec<(Time, usize)>, end_time: Time) {
885 let t = (pts.last().unwrap().0 + self.window_size + Duration::seconds(0.1)).min(end_time);
887 if pts.last().unwrap().0 != t {
888 pts.push((t, self.count(t)));
889 }
890
891 if pts.last().unwrap().0 != end_time {
892 pts.push((end_time, self.count(end_time)));
893 }
894 }
895}