map_model/make/
transit.rs

1use std::collections::{HashMap, HashSet};
2
3use anyhow::Result;
4
5use abstutil::Timer;
6use geom::{Distance, Duration, FindClosest, HashablePt2D, Time};
7use raw_map::{RawMap, RawTransitRoute, RawTransitStop, RawTransitType};
8
9use crate::make::match_points_to_lanes;
10use crate::{
11    LaneID, Map, PathConstraints, Position, TransitRoute, TransitRouteID, TransitStop,
12    TransitStopID,
13};
14
15pub fn finalize_transit(map: &mut Map, raw: &RawMap, timer: &mut Timer) {
16    // Snap stops to sidewalks and driving lanes, similar to buildings
17    let mut query: HashSet<HashablePt2D> = HashSet::new();
18    for stop in raw.transit_stops.values() {
19        query.insert(stop.position.to_hashable());
20    }
21    let sidewalk_pts = match_points_to_lanes(
22        map,
23        query,
24        |l| l.is_walkable(),
25        // Stops can be very close to intersections
26        Distance::ZERO,
27        // Stops shouldn't be far from sidewalks
28        Distance::meters(3.0),
29        timer,
30    );
31
32    // Create all stops
33    let mut gtfs_to_stop_id: HashMap<String, TransitStopID> = HashMap::new();
34    for stop in raw.transit_stops.values() {
35        if let Err(err) = create_stop(stop, &sidewalk_pts, &mut gtfs_to_stop_id, map) {
36            warn!("Couldn't create stop {}: {}", stop.gtfs_id, err);
37        }
38    }
39
40    let snapper = BorderSnapper::new(map);
41    for route in &raw.transit_routes {
42        if let Err(err) = create_route(route, map, &gtfs_to_stop_id, &snapper) {
43            warn!(
44                "Couldn't snap route {} ({}): {}",
45                route.gtfs_id, route.short_name, err
46            );
47        }
48    }
49
50    // TODO Clean up unused stops; maybe one of the routes didn't work. Re-map IDs...
51}
52
53fn create_stop(
54    stop: &RawTransitStop,
55    sidewalk_pts: &HashMap<HashablePt2D, Position>,
56    gtfs_to_stop_id: &mut HashMap<String, TransitStopID>,
57    map: &mut Map,
58) -> Result<()> {
59    // TODO We'll have to look up all routes referencing this stop and determine this
60    let vehicle = PathConstraints::Bus;
61    if let Some(sidewalk_pos) = sidewalk_pts.get(&stop.position.to_hashable()) {
62        let sidewalk_lane = sidewalk_pos.lane();
63        if let Some(driving_pos) = map
64            .get_parent(sidewalk_lane)
65            .find_closest_lane(sidewalk_lane, |l| vehicle.can_use(l, map))
66            .map(|l| sidewalk_pos.equiv_pos(l, map))
67        {
68            let road = sidewalk_lane.road;
69            let id = TransitStopID {
70                road,
71                idx: map.get_r(road).transit_stops.len(),
72            };
73            map.mut_road(road).transit_stops.insert(id);
74            map.transit_stops.insert(
75                id,
76                TransitStop {
77                    id,
78                    name: stop.name.clone(),
79                    gtfs_id: stop.gtfs_id.clone(),
80                    driving_pos,
81                    sidewalk_pos: *sidewalk_pos,
82                    is_train_stop: vehicle == PathConstraints::Train,
83                },
84            );
85            gtfs_to_stop_id.insert(stop.gtfs_id.clone(), id);
86            Ok(())
87        } else {
88            bail!(
89                "Couldn't find a lane for {:?} next to sidewalk {}",
90                vehicle,
91                sidewalk_lane
92            );
93        }
94    } else {
95        bail!("Stop position {} wasn't close to a sidewalk", stop.position);
96    }
97}
98
99struct BorderSnapper {
100    bus_incoming_borders: FindClosest<LaneID>,
101    bus_outgoing_borders: FindClosest<LaneID>,
102    train_incoming_borders: FindClosest<LaneID>,
103    train_outgoing_borders: FindClosest<LaneID>,
104}
105
106impl BorderSnapper {
107    fn new(map: &Map) -> BorderSnapper {
108        let mut snapper = BorderSnapper {
109            bus_incoming_borders: FindClosest::new(),
110            bus_outgoing_borders: FindClosest::new(),
111            train_incoming_borders: FindClosest::new(),
112            train_outgoing_borders: FindClosest::new(),
113        };
114        for i in map.all_incoming_borders() {
115            for l in i.get_outgoing_lanes(map, PathConstraints::Bus) {
116                // TODO FindClosest doesn't handle single points as geometries, so use the lane
117                // polygon
118                snapper
119                    .bus_incoming_borders
120                    .add_polygon(l, &map.get_l(l).get_thick_polygon());
121            }
122            for l in i.get_outgoing_lanes(map, PathConstraints::Train) {
123                snapper
124                    .train_incoming_borders
125                    .add_polygon(l, &map.get_l(l).get_thick_polygon());
126            }
127        }
128        for i in map.all_outgoing_borders() {
129            for l in i.get_incoming_lanes(map, PathConstraints::Bus) {
130                snapper
131                    .bus_outgoing_borders
132                    .add_polygon(l, &map.get_l(l).get_thick_polygon());
133            }
134            for l in i.get_incoming_lanes(map, PathConstraints::Train) {
135                snapper
136                    .train_outgoing_borders
137                    .add_polygon(l, &map.get_l(l).get_thick_polygon());
138            }
139        }
140        snapper
141    }
142}
143
144fn create_route(
145    route: &RawTransitRoute,
146    map: &mut Map,
147    gtfs_to_stop_id: &HashMap<String, TransitStopID>,
148    snapper: &BorderSnapper,
149) -> Result<()> {
150    // TODO At least warn about stops that failed to snap
151    let stops: Vec<TransitStopID> = route
152        .stops
153        .iter()
154        .filter_map(|gtfs_id| gtfs_to_stop_id.get(gtfs_id).cloned())
155        .collect();
156    if stops.is_empty() {
157        bail!("No valid stops");
158    }
159    let border_snap_threshold = Distance::meters(30.0);
160
161    let start = if map.boundary_polygon.contains_pt(route.shape.first_pt()) {
162        map.get_ts(stops[0]).driving_pos.lane()
163    } else {
164        // Find the first time the route shape hits the map boundary
165        let entry_pt = *map
166            .boundary_polygon
167            .get_outer_ring()
168            .all_intersections(&route.shape)
169            .get(0)
170            .ok_or_else(|| anyhow!("couldn't find where shape enters map"))?;
171        // Snap that to a border
172        let borders = if route.route_type == RawTransitType::Bus {
173            &snapper.bus_incoming_borders
174        } else {
175            &snapper.train_incoming_borders
176        };
177        match borders.closest_pt(entry_pt, border_snap_threshold) {
178            Some((l, _)) => l,
179            None => bail!(
180                "Couldn't find a {:?} border near start {}",
181                route.route_type,
182                entry_pt
183            ),
184        }
185    };
186
187    let end_border = if map.boundary_polygon.contains_pt(route.shape.last_pt()) {
188        None
189    } else {
190        // Find the last time the route shape hits the map boundary
191        let exit_pt = *map
192            .boundary_polygon
193            .get_outer_ring()
194            .all_intersections(&route.shape)
195            .last()
196            .ok_or_else(|| anyhow!("couldn't find where shape leaves map"))?;
197        // Snap that to a border
198        let borders = if route.route_type == RawTransitType::Bus {
199            &snapper.bus_outgoing_borders
200        } else {
201            &snapper.train_outgoing_borders
202        };
203        match borders.closest_pt(exit_pt, border_snap_threshold) {
204            Some((lane, _)) => {
205                // Edge case: the last stop is on the same road as the border. We can't lane-change
206                // suddenly, so match the lane in that case.
207                let last_stop_lane = map.get_ts(*stops.last().unwrap()).driving_pos.lane();
208                Some(if lane.road == last_stop_lane.road {
209                    last_stop_lane
210                } else {
211                    lane
212                })
213            }
214            None => bail!(
215                "Couldn't find a {:?} border near end {}",
216                route.route_type,
217                exit_pt
218            ),
219        }
220    };
221
222    // TODO This'll come from the RawTransitRoute eventually. For now, every 30 minutes.
223    let spawn_times: Vec<Time> = (0..48)
224        .map(|i| Time::START_OF_DAY + (i as f64) * Duration::minutes(30))
225        .collect();
226
227    let result = TransitRoute {
228        id: TransitRouteID(map.transit_routes.len()),
229        long_name: route.long_name.clone(),
230        short_name: route.short_name.clone(),
231        gtfs_id: route.gtfs_id.clone(),
232        stops,
233        start,
234        end_border,
235        route_type: match route.route_type {
236            RawTransitType::Bus => PathConstraints::Bus,
237            RawTransitType::Train => PathConstraints::Train,
238        },
239        spawn_times: spawn_times.clone(),
240        orig_spawn_times: spawn_times,
241    };
242
243    // Check that the paths are valid
244    result.all_paths(map)?;
245
246    map.transit_routes.push(result);
247    Ok(())
248}