importer/soundcast/
trips.rs

1use std::collections::HashMap;
2
3use abstutil::{prettyprint_usize, MultiMap, Timer};
4use geom::PolyLine;
5use map_model::{osm, BuildingID, Map, Path, PathConstraints, PathRequest, PathStep};
6use synthpop::{
7    IndividTrip, MapBorder, MapBorders, OrigPersonID, PersonSpec, Scenario, TripEndpoint, TripMode,
8};
9
10use crate::soundcast::popdat::{Endpoint, OrigTrip, PopDat};
11
12#[derive(Clone, Debug)]
13struct Trip {
14    from: TripEndpoint,
15    to: TripEndpoint,
16    orig: OrigTrip,
17}
18
19/// Transform the Seattle-wide `Endpoints` into specific `TripEndpoints` for this map. When the
20/// endpoint happens to be a building on the map, this is straightforward. Otherwise, the endpoint
21/// will snap to a border intersection.
22///
23/// When `only_passthrough_trips` is true, only trips beginning and ending off-map are returned.
24/// When it's false, all other trips are returned.
25#[allow(clippy::too_many_arguments)]
26fn endpoints(
27    from: &Endpoint,
28    to: &Endpoint,
29    map: &Map,
30    osm_id_to_bldg: &HashMap<osm::OsmID, BuildingID>,
31    (in_borders, out_borders): (&Vec<MapBorder>, &Vec<MapBorder>),
32    constraints: PathConstraints,
33    maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>,
34    only_passthrough_trips: bool,
35) -> Option<(TripEndpoint, TripEndpoint)> {
36    let from_bldg = from
37        .osm_building
38        .and_then(|id| osm_id_to_bldg.get(&id))
39        .map(|b| TripEndpoint::Building(*b));
40    let to_bldg = to
41        .osm_building
42        .and_then(|id| osm_id_to_bldg.get(&id))
43        .map(|b| TripEndpoint::Building(*b));
44
45    if only_passthrough_trips {
46        if from_bldg.is_some() || to_bldg.is_some() {
47            return None;
48        }
49    } else {
50        // Easy case: totally within the map
51        if let (Some(b1), Some(b2)) = (from_bldg, to_bldg) {
52            return Some((b1, b2));
53        }
54    }
55
56    // If it's a pass-through trip, check if the straight line between the endpoints even crosses
57    // the map boundary. If not, the trip probably doesn't even involve this map at all. There are
58    // false positives and negatives with this approach; we could be more accurate by pathfinding
59    // on the huge_map, but that would be incredibly slow.
60    if from_bldg.is_none() && to_bldg.is_none() {
61        // TODO Don't enable pass-through trips yet in general. The time to generate the scenario, the
62        // resulting scenario file, and the simulation runtime (and gridlockiness) all skyrocket.
63        // Need to harden more things before enabling.
64        if !only_passthrough_trips {
65            return None;
66        }
67
68        if let Ok(pl) = PolyLine::new(vec![
69            from.pos.to_pt(map.get_gps_bounds()),
70            to.pos.to_pt(map.get_gps_bounds()),
71        ]) {
72            if !map.get_boundary_polygon().intersects_polyline(&pl) {
73                return None;
74            }
75        }
76    }
77
78    // TODO When the trip begins or ends at a border, it'd be nice to fix depart_at, trip_time, and
79    // trip_dist. Assume constant speed through the trip. But when I last tried this, the distance
80    // was way off. :\
81
82    let snapper = BorderSnapper::new(from, to, constraints, maybe_huge_map)
83        .unwrap_or(BorderSnapper { path: None });
84
85    let from_endpt = from_bldg
86        .or_else(|| snapper.snap_border(in_borders, true, map, maybe_huge_map))
87        .or_else(|| {
88            // Fallback to finding the nearest border with straight-line distance
89            in_borders
90                .iter()
91                .min_by_key(|border| border.gps_pos.fast_dist(from.pos))
92                .map(|border| TripEndpoint::Border(border.i))
93        })?;
94    let to_endpt = to_bldg
95        .or_else(|| snapper.snap_border(out_borders, false, map, maybe_huge_map))
96        .or_else(|| {
97            // Fallback to finding the nearest border with straight-line distance
98            out_borders
99                .iter()
100                .min_by_key(|border| border.gps_pos.fast_dist(from.pos))
101                .map(|border| TripEndpoint::Border(border.i))
102        })?;
103
104    if from_endpt == to_endpt {
105        //warn!("loop on {:?}. {:?} to {:?}", from_endpt, from, to);
106    }
107
108    Some((from_endpt, to_endpt))
109}
110
111// Use the large map to find the real path somebody might take, then try to match that to a border
112// in the smaller map.
113struct BorderSnapper {
114    path: Option<Path>,
115}
116
117impl BorderSnapper {
118    fn new(
119        from: &Endpoint,
120        to: &Endpoint,
121        constraints: PathConstraints,
122        maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>,
123    ) -> Option<BorderSnapper> {
124        let (huge_map, huge_osm_id_to_bldg) = maybe_huge_map?;
125        let b1 = *from
126            .osm_building
127            .and_then(|id| huge_osm_id_to_bldg.get(&id))?;
128        let b2 = *to
129            .osm_building
130            .and_then(|id| huge_osm_id_to_bldg.get(&id))?;
131        let req = PathRequest::between_buildings(huge_map, b1, b2, constraints)?;
132        Some(BorderSnapper {
133            path: huge_map.pathfind(req).ok(),
134        })
135    }
136
137    fn snap_border(
138        &self,
139        usable_borders: &[MapBorder],
140        incoming: bool,
141        map: &Map,
142        maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>,
143    ) -> Option<TripEndpoint> {
144        let huge_map = maybe_huge_map?.0;
145        // Do any of the usable borders match the path?
146        // TODO Calculate this once
147        let mut node_id_to_border = HashMap::new();
148        for border in usable_borders {
149            node_id_to_border.insert(map.get_i(border.i).orig_id, border.i);
150        }
151        let mut iter1;
152        let mut iter2;
153        let steps: &mut dyn Iterator<Item = &PathStep> = if incoming {
154            iter1 = self.path.as_ref()?.get_steps().iter();
155            &mut iter1
156        } else {
157            iter2 = self.path.as_ref()?.get_steps().iter().rev();
158            &mut iter2
159        };
160        for step in steps {
161            if let PathStep::Turn(t) | PathStep::ContraflowTurn(t) = step {
162                if let Some(i) = node_id_to_border.get(&huge_map.get_i(t.parent).orig_id) {
163                    return Some(TripEndpoint::Border(*i));
164                }
165            }
166        }
167        None
168    }
169}
170
171fn clip_trips(
172    map: &Map,
173    popdat: &PopDat,
174    huge_map: &Map,
175    only_passthrough_trips: bool,
176    timer: &mut Timer,
177) -> Vec<Trip> {
178    let maybe_huge_map = if map.get_name().map == "huge_seattle" {
179        None
180    } else {
181        let mut huge_osm_id_to_bldg = HashMap::new();
182        for b in huge_map.all_buildings() {
183            huge_osm_id_to_bldg.insert(b.orig_id, b.id);
184        }
185        Some((huge_map, huge_osm_id_to_bldg))
186    };
187
188    let mut osm_id_to_bldg = HashMap::new();
189    for b in map.all_buildings() {
190        osm_id_to_bldg.insert(b.orig_id, b.id);
191    }
192    let borders = MapBorders::new(map);
193
194    let total_trips = popdat.trips.len();
195    let maybe_results: Vec<Option<Trip>> =
196        timer.parallelize("clip trips", popdat.trips.iter().collect(), |orig| {
197            let (from, to) = endpoints(
198                &orig.from,
199                &orig.to,
200                map,
201                &osm_id_to_bldg,
202                borders.for_mode(orig.mode),
203                match orig.mode {
204                    TripMode::Walk | TripMode::Transit => PathConstraints::Pedestrian,
205                    TripMode::Drive => PathConstraints::Car,
206                    TripMode::Bike => PathConstraints::Bike,
207                },
208                maybe_huge_map.as_ref(),
209                only_passthrough_trips,
210            )?;
211            Some(Trip {
212                from,
213                to,
214                orig: orig.clone(),
215            })
216        });
217    let trips: Vec<Trip> = maybe_results.into_iter().flatten().collect();
218
219    info!(
220        "{} trips clipped down to just {}",
221        prettyprint_usize(total_trips),
222        prettyprint_usize(trips.len())
223    );
224
225    trips
226}
227
228pub fn make_scenario(
229    scenario_name: &str,
230    map: &Map,
231    popdat: &PopDat,
232    huge_map: &Map,
233    timer: &mut Timer,
234) -> Scenario {
235    let only_passthrough_trips = scenario_name == "passthrough";
236
237    let mut individ_trips: Vec<Option<IndividTrip>> = Vec::new();
238    // person -> (trip seq, index into individ_trips)
239    let mut trips_per_person: MultiMap<OrigPersonID, ((usize, bool, usize), usize)> =
240        MultiMap::new();
241    for trip in clip_trips(map, popdat, huge_map, only_passthrough_trips, timer) {
242        let idx = individ_trips.len();
243        individ_trips.push(Some(IndividTrip::new(
244            trip.orig.depart_at,
245            trip.orig.purpose,
246            trip.from,
247            trip.to,
248            trip.orig.mode,
249        )));
250        trips_per_person.insert(trip.orig.person, (trip.orig.seq, idx));
251    }
252    info!(
253        "{} clipped trips, over {} people",
254        prettyprint_usize(individ_trips.len()),
255        prettyprint_usize(trips_per_person.len())
256    );
257
258    let mut people = Vec::new();
259    for (orig_id, seq_trips) in trips_per_person.consume() {
260        let mut trips = Vec::new();
261        for (_, idx) in seq_trips {
262            trips.push(individ_trips[idx].take().unwrap());
263        }
264        // Actually, the sequence in the Soundcast dataset crosses midnight. Don't do that; sort by
265        // departure time starting with midnight.
266        trips.sort_by_key(|t| t.depart);
267        // Sanity check that endpoints match up
268        for pair in trips.windows(2) {
269            let destination = &pair[0].destination;
270            let origin = &pair[1].origin;
271            if destination != origin {
272                warn!(
273                    "Skipping {:?}, with adjacent trips that warp from {:?} to {:?}",
274                    orig_id, destination, origin
275                );
276                continue;
277            }
278        }
279
280        people.push(PersonSpec {
281            orig_id: Some(orig_id),
282            trips,
283        });
284    }
285    for maybe_t in individ_trips {
286        if maybe_t.is_some() {
287            panic!("Some IndividTrip wasn't associated with a Person?!");
288        }
289    }
290
291    Scenario {
292        scenario_name: scenario_name.to_string(),
293        map_name: map.get_name().clone(),
294        people,
295        only_seed_buses: None,
296    }
297    .remove_weird_schedules(true)
298}