ltn/logic/
impact.rs

1use std::collections::BTreeSet;
2
3use abstio::MapName;
4use abstutil::Timer;
5use geom::{Duration, Time};
6use map_gui::tools::compare_counts::CompareCounts;
7use map_model::{PathConstraints, PathRequest, PathV2, Pathfinder, RoadID};
8use synthpop::{Scenario, TrafficCounts, TripEndpoint, TripMode};
9use widgetry::EventCtx;
10
11use crate::App;
12
13// TODO Configurable main road penalty, like in the pathfinding tool
14// TODO Share structure or pieces with Ungap's predict mode
15// ... can't we just produce data of a certain shape, and have a UI pretty tuned for that?
16
17// This gets incrementally recalculated when stuff changes.
18//
19// - all_trips and everything else depends just on the map (we only have one scenario per map now)
20// - filtered_trips depend on filters
21// - the 'b' and 'relative' parts of compare_counts depend on map_edit_key
22pub struct Impact {
23    pub map: MapName,
24    pub filters: Filters,
25
26    // Handles all modes
27    // TODO Maybe try to use this app-wide
28    pathfinder_before_changes: Pathfinder,
29
30    all_trips: Vec<PathRequest>,
31    // A subset of all_trips, and the number of times somebody takes the same trip
32    filtered_trips: Vec<(PathRequest, usize)>,
33
34    pub compare_counts: CompareCounts,
35    pub map_edit_key: usize,
36}
37
38#[derive(PartialEq)]
39pub struct Filters {
40    pub modes: BTreeSet<TripMode>,
41    // TODO Has no effect yet. Do we need to store the TripEndpoints / can we detect from the
42    // PathRequest reasonably?
43    pub include_borders: bool,
44    pub departure_time: (Time, Time),
45}
46
47impl Impact {
48    pub fn empty(ctx: &EventCtx) -> Self {
49        Self {
50            map: MapName::blank(),
51            filters: Filters {
52                modes: vec![TripMode::Drive].into_iter().collect(),
53                include_borders: true,
54                departure_time: (Time::START_OF_DAY, end_of_day()),
55            },
56
57            pathfinder_before_changes: Pathfinder::empty(),
58
59            all_trips: Vec::new(),
60            filtered_trips: Vec::new(),
61
62            compare_counts: CompareCounts::empty(ctx),
63            map_edit_key: usize::MAX,
64        }
65    }
66
67    pub fn from_scenario(
68        ctx: &mut EventCtx,
69        app: &App,
70        scenario: Scenario,
71        timer: &mut Timer,
72    ) -> Impact {
73        let mut impact = Impact::empty(ctx);
74        let map = &app.per_map.map;
75
76        impact.pathfinder_before_changes = Pathfinder::new_ch(
77            map,
78            app.per_map.routing_params_before_changes.clone(),
79            PathConstraints::all(),
80            timer,
81        );
82
83        impact.map = app.per_map.map.get_name().clone();
84        impact.map_edit_key = app.per_map.map.get_edits_change_key();
85        impact.all_trips = timer
86            .parallelize("analyze trips", scenario.all_trips().collect(), |trip| {
87                TripEndpoint::path_req(trip.origin, trip.destination, trip.mode, map)
88            })
89            .into_iter()
90            .flatten()
91            .collect();
92        impact.trips_changed(ctx, app, timer);
93        impact.compare_counts.autoselect_layer();
94        impact
95    }
96
97    // TODO Cache? It depends both on the map_edit_key and modes belonging to filtered_trips.
98    fn pathfinder_after(&self, app: &App, timer: &mut Timer) -> Pathfinder {
99        let constraints: BTreeSet<PathConstraints> = self
100            .filters
101            .modes
102            .iter()
103            .map(|m| m.to_constraints())
104            .collect();
105        Pathfinder::new_ch(
106            &app.per_map.map,
107            app.per_map.map.routing_params_respecting_modal_filters(),
108            constraints.into_iter().collect(),
109            timer,
110        )
111    }
112
113    pub fn trips_changed(&mut self, ctx: &mut EventCtx, app: &App, timer: &mut Timer) {
114        let map = &app.per_map.map;
115        let constraints: BTreeSet<PathConstraints> = self
116            .filters
117            .modes
118            .iter()
119            .map(|m| m.to_constraints())
120            .collect();
121        self.filtered_trips = PathRequest::deduplicate(
122            map,
123            self.all_trips
124                .iter()
125                .filter(|req| constraints.contains(&req.constraints))
126                .cloned()
127                .collect(),
128        );
129
130        let counts_a = TrafficCounts::from_path_requests(
131            map,
132            // Don't bother describing all the trip filtering
133            "before filters".to_string(),
134            &self.filtered_trips,
135            &self.pathfinder_before_changes,
136            timer,
137        );
138
139        let counts_b = self.counts_b(app, timer);
140
141        let clickable_roads = true;
142        self.compare_counts = CompareCounts::new(
143            ctx,
144            app,
145            counts_a,
146            counts_b,
147            self.compare_counts.layer,
148            clickable_roads,
149        );
150    }
151
152    pub fn map_edits_changed(&mut self, ctx: &mut EventCtx, app: &App, timer: &mut Timer) {
153        self.map_edit_key = app.per_map.map.get_edits_change_key();
154        let counts_b = self.counts_b(app, timer);
155        self.compare_counts.recalculate_b(ctx, app, counts_b);
156    }
157
158    fn counts_b(&self, app: &App, timer: &mut Timer) -> TrafficCounts {
159        let map = &app.per_map.map;
160        let pathfinder_after = self.pathfinder_after(app, timer);
161
162        // We can't simply use TrafficCounts::from_path_requests. Due to spurious diffs with paths,
163        // we need to skip cases where the path before and after have the same cost. It's easiest
164        // (code-wise) to just repeat some calculation here.
165        let mut counts = TrafficCounts::from_path_requests(
166            map,
167            // Don't bother describing all the trip filtering
168            "after filters".to_string(),
169            &vec![],
170            &pathfinder_after,
171            timer,
172        );
173
174        timer.start_iter("calculate routes", self.filtered_trips.len());
175        for (req, count) in &self.filtered_trips {
176            timer.next();
177            if let (Some(path1), Some(path2)) = (
178                self.pathfinder_before_changes.pathfind_v2(req.clone(), map),
179                pathfinder_after.pathfind_v2(req.clone(), map),
180            ) {
181                if path1.get_cost() == path2.get_cost() {
182                    // When the path maybe changed but the cost is the same, just count it the same
183                    // as the original path
184                    counts.update_with_path(path1, *count, map);
185                } else {
186                    counts.update_with_path(path2, *count, map);
187                }
188            }
189        }
190
191        counts
192    }
193
194    /// Returns routes that start or stop crossing the given road. Returns paths (before filters,
195    /// after)
196    pub fn find_changed_routes(
197        &self,
198        app: &App,
199        r: RoadID,
200        timer: &mut Timer,
201    ) -> Vec<(PathV2, PathV2)> {
202        let map = &app.per_map.map;
203        let pathfinder_after = self.pathfinder_after(app, timer);
204
205        let mut changed = Vec::new();
206        timer.start_iter("find changed routes", self.filtered_trips.len());
207        for (req, _) in &self.filtered_trips {
208            timer.next();
209            if let (Some(path1), Some(path2)) = (
210                self.pathfinder_before_changes.pathfind_v2(req.clone(), map),
211                pathfinder_after.pathfind_v2(req.clone(), map),
212            ) {
213                // Skip spurious changes where the cost matches.
214                if path1.get_cost() == path2.get_cost() {
215                    continue;
216                }
217
218                if path1.crosses_road(r) != path2.crosses_road(r) {
219                    changed.push((path1, path2));
220                }
221            }
222        }
223        changed
224    }
225}
226
227// TODO Fixed, and sadly not const
228pub fn end_of_day() -> Time {
229    Time::START_OF_DAY + Duration::hours(24)
230}