synthpop/
endpoint.rs

1use geom::Pt2D;
2use map_model::{BuildingID, IntersectionID, Map, PathConstraints, PathRequest, Position};
3use serde::{Deserialize, Serialize};
4
5use crate::TripMode;
6
7/// Specifies where a trip begins or ends.
8#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
9pub enum TripEndpoint {
10    Building(BuildingID),
11    Border(IntersectionID),
12    /// Used for interactive spawning, tests, etc. For now, only valid as a trip's start.
13    SuddenlyAppear(Position),
14}
15
16impl TripEndpoint {
17    /// Returns a point representing where this endpoint is.
18    pub fn pt(self, map: &Map) -> Pt2D {
19        match self {
20            TripEndpoint::Building(b) => map.get_b(b).polygon.center(),
21            TripEndpoint::Border(i) => map.get_i(i).polygon.center(),
22            TripEndpoint::SuddenlyAppear(pos) => pos.pt(map),
23        }
24    }
25
26    /// Figure out a single PathRequest that goes between two TripEndpoints. Assume a single mode
27    /// the entire time -- no walking to a car before driving, for instance. The result probably
28    /// won't be exactly what would happen on a real trip between the endpoints because of this
29    /// assumption.
30    pub fn path_req(
31        from: TripEndpoint,
32        to: TripEndpoint,
33        mode: TripMode,
34        map: &Map,
35    ) -> Option<PathRequest> {
36        let start = from.pos(mode, true, map)?;
37        let end = to.pos(mode, false, map)?;
38        Some(match mode {
39            TripMode::Walk | TripMode::Transit => PathRequest::walking(start, end),
40            TripMode::Bike => PathRequest::vehicle(start, end, PathConstraints::Bike),
41            // Only cars leaving from a building might turn out from the driveway in a special way
42            TripMode::Drive => {
43                if matches!(from, TripEndpoint::Building(_)) {
44                    PathRequest::leave_from_driveway(start, end, PathConstraints::Car, map)
45                } else {
46                    PathRequest::vehicle(start, end, PathConstraints::Car)
47                }
48            }
49        })
50    }
51
52    fn pos(self, mode: TripMode, from: bool, map: &Map) -> Option<Position> {
53        match mode {
54            TripMode::Walk | TripMode::Transit => self.sidewalk_pos(map, from),
55            TripMode::Drive | TripMode::Bike => {
56                let constraints = mode.to_constraints();
57                if from {
58                    match self {
59                        // Fall through
60                        TripEndpoint::Building(_) => {}
61                        TripEndpoint::Border(i) => {
62                            return map.get_i(i).some_outgoing_road(map).and_then(|dr| {
63                                dr.lanes(constraints, map)
64                                    .get(0)
65                                    .map(|l| Position::start(*l))
66                            });
67                        }
68                        TripEndpoint::SuddenlyAppear(pos) => {
69                            return Some(pos);
70                        }
71                    }
72                }
73
74                match self {
75                    TripEndpoint::Building(b) => match constraints {
76                        PathConstraints::Car => {
77                            let driving_lane = map.find_driving_lane_near_building(b);
78                            let sidewalk_pos = map.get_b(b).sidewalk_pos;
79                            if driving_lane.road == sidewalk_pos.lane().road {
80                                Some(sidewalk_pos.equiv_pos(driving_lane, map))
81                            } else {
82                                Some(Position::start(driving_lane))
83                            }
84                        }
85                        PathConstraints::Bike => Some(map.get_b(b).biking_connection(map)?.0),
86                        PathConstraints::Bus
87                        | PathConstraints::Train
88                        | PathConstraints::Pedestrian => {
89                            unreachable!()
90                        }
91                    },
92                    TripEndpoint::Border(i) => {
93                        map.get_i(i).some_incoming_road(map).and_then(|dr| {
94                            let lanes = dr.lanes(constraints, map);
95                            if lanes.is_empty() {
96                                None
97                            } else {
98                                // TODO ideally could use any
99                                Some(Position::end(lanes[0], map))
100                            }
101                        })
102                    }
103                    // This can also be an ending, despite the naming
104                    TripEndpoint::SuddenlyAppear(pos) => Some(pos),
105                }
106            }
107        }
108    }
109
110    fn sidewalk_pos(self, map: &Map, from: bool) -> Option<Position> {
111        match self {
112            TripEndpoint::Building(b) => Some(map.get_b(b).sidewalk_pos),
113            TripEndpoint::Border(i) => {
114                if from {
115                    TripEndpoint::start_walking_at_border(i, map)
116                } else {
117                    TripEndpoint::end_walking_at_border(i, map)
118                }
119            }
120            TripEndpoint::SuddenlyAppear(pos) => Some(pos),
121        }
122    }
123
124    // Recall sidewalks are bidirectional.
125    pub fn start_walking_at_border(i: IntersectionID, map: &Map) -> Option<Position> {
126        let lanes = map
127            .get_i(i)
128            .get_outgoing_lanes(map, PathConstraints::Pedestrian);
129        if !lanes.is_empty() {
130            return Some(Position::start(lanes[0]));
131        }
132        map.get_i(i)
133            .get_incoming_lanes(map, PathConstraints::Pedestrian)
134            .get(0)
135            .map(|l| Position::end(*l, map))
136    }
137
138    pub fn end_walking_at_border(i: IntersectionID, map: &Map) -> Option<Position> {
139        if let Some(l) = map
140            .get_i(i)
141            .get_incoming_lanes(map, PathConstraints::Pedestrian)
142            .get(0)
143        {
144            return Some(Position::end(*l, map));
145        }
146
147        let lanes = map
148            .get_i(i)
149            .get_outgoing_lanes(map, PathConstraints::Pedestrian);
150        if lanes.is_empty() {
151            return None;
152        }
153        Some(Position::start(lanes[0]))
154    }
155}