diff --git a/sim/src/lib.rs b/sim/src/lib.rs index 7b64d885b0..b4d6084d33 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -462,45 +462,15 @@ impl SidewalkSpot { // Recall sidewalks are bidirectional. pub fn start_at_border(i: IntersectionID, map: &Map) -> Option { - let lanes = map - .get_i(i) - .get_outgoing_lanes(map, PathConstraints::Pedestrian); - if !lanes.is_empty() { - return Some(SidewalkSpot { - sidewalk_pos: Position::start(lanes[0]), - connection: SidewalkPOI::Border(i), - }); - } - - map.get_i(i) - .get_incoming_lanes(map, PathConstraints::Pedestrian) - .get(0) - .map(|l| SidewalkSpot { - sidewalk_pos: Position::end(*l, map), - connection: SidewalkPOI::Border(i), - }) + Some(SidewalkSpot { + sidewalk_pos: TripEndpoint::start_walking_at_border(i, map)?, + connection: SidewalkPOI::Border(i), + }) } pub fn end_at_border(i: IntersectionID, map: &Map) -> Option { - if let Some(l) = map - .get_i(i) - .get_incoming_lanes(map, PathConstraints::Pedestrian) - .get(0) - { - return Some(SidewalkSpot { - sidewalk_pos: Position::end(*l, map), - connection: SidewalkPOI::Border(i), - }); - } - - let lanes = map - .get_i(i) - .get_outgoing_lanes(map, PathConstraints::Pedestrian); - if lanes.is_empty() { - return None; - } Some(SidewalkSpot { - sidewalk_pos: Position::start(lanes[0]), + sidewalk_pos: TripEndpoint::end_walking_at_border(i, map)?, connection: SidewalkPOI::Border(i), }) } diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index 0a8f77635f..254ee5d0cb 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -223,7 +223,7 @@ impl TripSpec { } else { PathConstraints::Bike }; - let goal = to.driving_goal(constraints, map)?; + let goal = driving_goal(to, constraints, map)?; match from { TripEndpoint::Bldg(start_bldg) => { if mode == TripMode::Drive { @@ -266,12 +266,12 @@ impl TripSpec { } } TripMode::Walk => TripSpec::JustWalking { - start: from.start_sidewalk_spot(map)?, - goal: to.end_sidewalk_spot(map)?, + start: start_sidewalk_spot(from, map)?, + goal: end_sidewalk_spot(to, map)?, }, TripMode::Transit => { - let start = from.start_sidewalk_spot(map)?; - let goal = to.end_sidewalk_spot(map)?; + let start = start_sidewalk_spot(from, map)?; + let goal = end_sidewalk_spot(to, map)?; if let Some((stop1, maybe_stop2, route)) = map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos) { @@ -291,3 +291,47 @@ impl TripSpec { }) } } + +fn start_sidewalk_spot(endpt: TripEndpoint, map: &Map) -> Result { + match endpt { + TripEndpoint::Bldg(b) => Ok(SidewalkSpot::building(b, map)), + TripEndpoint::Border(i) => SidewalkSpot::start_at_border(i, map) + .ok_or_else(|| anyhow!("can't start walking from {}", i)), + TripEndpoint::SuddenlyAppear(pos) => Ok(SidewalkSpot::suddenly_appear(pos, map)), + } +} + +fn end_sidewalk_spot(endpt: TripEndpoint, map: &Map) -> Result { + match endpt { + TripEndpoint::Bldg(b) => Ok(SidewalkSpot::building(b, map)), + TripEndpoint::Border(i) => { + SidewalkSpot::end_at_border(i, map).ok_or_else(|| anyhow!("can't end walking at {}", i)) + } + TripEndpoint::SuddenlyAppear(_) => unreachable!(), + } +} + +fn driving_goal( + endpt: TripEndpoint, + constraints: PathConstraints, + map: &Map, +) -> Result { + match endpt { + TripEndpoint::Bldg(b) => Ok(DrivingGoal::ParkNear(b)), + // TODO Duplicates some logic from TripEndpoint::pos + TripEndpoint::Border(i) => map + .get_i(i) + .some_incoming_road(map) + .and_then(|dr| { + let lanes = dr.lanes(constraints, map); + if lanes.is_empty() { + None + } else { + // TODO ideally could use any + Some(DrivingGoal::Border(dr.dst_i(map), lanes[0])) + } + }) + .ok_or_else(|| anyhow!("can't end at {} for {:?}", i, constraints)), + TripEndpoint::SuddenlyAppear(_) => unreachable!(), + } +} diff --git a/sim/src/sim/scenario.rs b/sim/src/sim/scenario.rs index 132daa6a13..de876296ec 100644 --- a/sim/src/sim/scenario.rs +++ b/sim/src/sim/scenario.rs @@ -1,21 +1,18 @@ use std::collections::{BTreeMap, HashSet, VecDeque}; -use anyhow::Result; use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; use abstutil::{prettyprint_usize, Counter, Timer}; -use geom::{Distance, Pt2D, Speed}; -use map_model::{ - BuildingID, Map, OffstreetParking, PathConstraints, PathRequest, Position, RoadID, -}; +use geom::{Distance, Speed}; +use map_model::{BuildingID, Map, OffstreetParking, RoadID}; use synthpop::{PersonSpec, Scenario, TripEndpoint, TripMode}; use crate::make::fork_rng; use crate::{ - DrivingGoal, ParkingSpot, SidewalkSpot, Sim, StartTripArgs, TripInfo, Vehicle, VehicleSpec, - VehicleType, BIKE_LENGTH, MAX_CAR_LENGTH, MIN_CAR_LENGTH, + ParkingSpot, Sim, StartTripArgs, TripInfo, Vehicle, VehicleSpec, VehicleType, BIKE_LENGTH, + MAX_CAR_LENGTH, MIN_CAR_LENGTH, }; impl Sim { @@ -366,111 +363,3 @@ pub fn count_parked_cars_per_bldg(scenario: &Scenario) -> Counter { } per_bldg } - -/*impl TripEndpoint { - /// Figure out a single PathRequest that goes between two TripEndpoints. Assume a single mode - /// the entire time -- no walking to a car before driving, for instance. The result probably - /// won't be exactly what would happen on a real trip between the endpoints because of this - /// assumption. - pub fn path_req( - from: TripEndpoint, - to: TripEndpoint, - mode: TripMode, - map: &Map, - ) -> Option { - let start = from.pos(mode, true, map)?; - let end = to.pos(mode, false, map)?; - Some(match mode { - TripMode::Walk | TripMode::Transit => PathRequest::walking(start, end), - TripMode::Bike => PathRequest::vehicle(start, end, PathConstraints::Bike), - // Only cars leaving from a building might turn out from the driveway in a special way - TripMode::Drive => { - if matches!(from, TripEndpoint::Bldg(_)) { - PathRequest::leave_from_driveway(start, end, PathConstraints::Car, map) - } else { - PathRequest::vehicle(start, end, PathConstraints::Car) - } - } - }) - } - - fn start_sidewalk_spot(&self, map: &Map) -> Result { - match self { - TripEndpoint::Bldg(b) => Ok(SidewalkSpot::building(*b, map)), - TripEndpoint::Border(i) => SidewalkSpot::start_at_border(*i, map) - .ok_or_else(|| anyhow!("can't start walking from {}", i)), - TripEndpoint::SuddenlyAppear(pos) => Ok(SidewalkSpot::suddenly_appear(*pos, map)), - } - } - - fn end_sidewalk_spot(&self, map: &Map) -> Result { - match self { - TripEndpoint::Bldg(b) => Ok(SidewalkSpot::building(*b, map)), - TripEndpoint::Border(i) => SidewalkSpot::end_at_border(*i, map) - .ok_or_else(|| anyhow!("can't end walking at {}", i)), - TripEndpoint::SuddenlyAppear(_) => unreachable!(), - } - } - - fn driving_goal(&self, constraints: PathConstraints, map: &Map) -> Result { - match self { - TripEndpoint::Bldg(b) => Ok(DrivingGoal::ParkNear(*b)), - TripEndpoint::Border(i) => map - .get_i(*i) - .some_incoming_road(map) - .and_then(|dr| { - let lanes = dr.lanes(constraints, map); - if lanes.is_empty() { - None - } else { - // TODO ideally could use any - Some(DrivingGoal::Border(dr.dst_i(map), lanes[0])) - } - }) - .ok_or_else(|| anyhow!("can't end at {} for {:?}", i, constraints)), - TripEndpoint::SuddenlyAppear(_) => unreachable!(), - } - } - - fn pos(self, mode: TripMode, from: bool, map: &Map) -> Option { - match mode { - TripMode::Walk | TripMode::Transit => (if from { - self.start_sidewalk_spot(map) - } else { - self.end_sidewalk_spot(map) - }) - .ok() - .map(|spot| spot.sidewalk_pos), - TripMode::Drive | TripMode::Bike => { - if from { - match self { - // Fall through and use DrivingGoal also to start. - TripEndpoint::Bldg(_) => {} - TripEndpoint::Border(i) => { - return map.get_i(i).some_outgoing_road(map).and_then(|dr| { - dr.lanes(mode.to_constraints(), map) - .get(0) - .map(|l| Position::start(*l)) - }); - } - TripEndpoint::SuddenlyAppear(pos) => { - return Some(pos); - } - } - } - self.driving_goal(mode.to_constraints(), map) - .ok() - .and_then(|goal| goal.goal_pos(mode.to_constraints(), map)) - } - } - } - - /// Returns a point representing where this endpoint is. - pub fn pt(&self, map: &Map) -> Pt2D { - match self { - TripEndpoint::Bldg(b) => map.get_b(*b).polygon.center(), - TripEndpoint::Border(i) => map.get_i(*i).polygon.center(), - TripEndpoint::SuddenlyAppear(pos) => pos.pt(map), - } - } -}*/ diff --git a/synthpop/src/endpoint.rs b/synthpop/src/endpoint.rs new file mode 100644 index 0000000000..b75fe684b1 --- /dev/null +++ b/synthpop/src/endpoint.rs @@ -0,0 +1,154 @@ +use geom::Pt2D; +use map_model::{BuildingID, IntersectionID, Map, PathConstraints, PathRequest, Position}; +use serde::{Deserialize, Serialize}; + +use crate::TripMode; + +/// Specifies where a trip begins or ends. +#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum TripEndpoint { + Bldg(BuildingID), + Border(IntersectionID), + /// Used for interactive spawning, tests, etc. For now, only valid as a trip's start. + SuddenlyAppear(Position), +} + +impl TripEndpoint { + /// Returns a point representing where this endpoint is. + pub fn pt(self, map: &Map) -> Pt2D { + match self { + TripEndpoint::Bldg(b) => map.get_b(b).polygon.center(), + TripEndpoint::Border(i) => map.get_i(i).polygon.center(), + TripEndpoint::SuddenlyAppear(pos) => pos.pt(map), + } + } + + /// Figure out a single PathRequest that goes between two TripEndpoints. Assume a single mode + /// the entire time -- no walking to a car before driving, for instance. The result probably + /// won't be exactly what would happen on a real trip between the endpoints because of this + /// assumption. + pub fn path_req( + from: TripEndpoint, + to: TripEndpoint, + mode: TripMode, + map: &Map, + ) -> Option { + let start = from.pos(mode, true, map)?; + let end = to.pos(mode, false, map)?; + Some(match mode { + TripMode::Walk | TripMode::Transit => PathRequest::walking(start, end), + TripMode::Bike => PathRequest::vehicle(start, end, PathConstraints::Bike), + // Only cars leaving from a building might turn out from the driveway in a special way + TripMode::Drive => { + if matches!(from, TripEndpoint::Bldg(_)) { + PathRequest::leave_from_driveway(start, end, PathConstraints::Car, map) + } else { + PathRequest::vehicle(start, end, PathConstraints::Car) + } + } + }) + } + + fn pos(self, mode: TripMode, from: bool, map: &Map) -> Option { + match mode { + TripMode::Walk | TripMode::Transit => self.sidewalk_pos(map, from), + TripMode::Drive | TripMode::Bike => { + let constraints = mode.to_constraints(); + if from { + match self { + // Fall through + TripEndpoint::Bldg(_) => {} + TripEndpoint::Border(i) => { + return map.get_i(i).some_outgoing_road(map).and_then(|dr| { + dr.lanes(constraints, map) + .get(0) + .map(|l| Position::start(*l)) + }); + } + TripEndpoint::SuddenlyAppear(pos) => { + return Some(pos); + } + } + } + + match self { + TripEndpoint::Bldg(b) => match constraints { + PathConstraints::Car => { + let driving_lane = map.find_driving_lane_near_building(b); + let sidewalk_pos = map.get_b(b).sidewalk_pos; + if driving_lane.road == sidewalk_pos.lane().road { + Some(sidewalk_pos.equiv_pos(driving_lane, map)) + } else { + Some(Position::start(driving_lane)) + } + } + PathConstraints::Bike => Some(map.get_b(b).biking_connection(map)?.0), + PathConstraints::Bus + | PathConstraints::Train + | PathConstraints::Pedestrian => { + unreachable!() + } + }, + TripEndpoint::Border(i) => { + map.get_i(i).some_incoming_road(map).and_then(|dr| { + let lanes = dr.lanes(constraints, map); + if lanes.is_empty() { + None + } else { + // TODO ideally could use any + Some(Position::end(lanes[0], map)) + } + }) + } + TripEndpoint::SuddenlyAppear(_) => unreachable!(), + } + } + } + } + + fn sidewalk_pos(self, map: &Map, from: bool) -> Option { + match self { + TripEndpoint::Bldg(b) => Some(map.get_b(b).sidewalk_pos), + TripEndpoint::Border(i) => { + if from { + TripEndpoint::start_walking_at_border(i, map) + } else { + TripEndpoint::end_walking_at_border(i, map) + } + } + TripEndpoint::SuddenlyAppear(pos) => Some(pos), + } + } + + // Recall sidewalks are bidirectional. + pub fn start_walking_at_border(i: IntersectionID, map: &Map) -> Option { + let lanes = map + .get_i(i) + .get_outgoing_lanes(map, PathConstraints::Pedestrian); + if !lanes.is_empty() { + return Some(Position::start(lanes[0])); + } + map.get_i(i) + .get_incoming_lanes(map, PathConstraints::Pedestrian) + .get(0) + .map(|l| Position::end(*l, map)) + } + + pub fn end_walking_at_border(i: IntersectionID, map: &Map) -> Option { + if let Some(l) = map + .get_i(i) + .get_incoming_lanes(map, PathConstraints::Pedestrian) + .get(0) + { + return Some(Position::end(*l, map)); + } + + let lanes = map + .get_i(i) + .get_outgoing_lanes(map, PathConstraints::Pedestrian); + if lanes.is_empty() { + return None; + } + Some(Position::start(lanes[0])) + } +} diff --git a/synthpop/src/lib.rs b/synthpop/src/lib.rs index 3099d86ed8..695225ae7c 100644 --- a/synthpop/src/lib.rs +++ b/synthpop/src/lib.rs @@ -6,25 +6,18 @@ extern crate log; use serde::{Deserialize, Serialize}; use abstutil::{deserialize_usize, serialize_usize}; -use map_model::{BuildingID, IntersectionID, PathConstraints, Position}; +use map_model::PathConstraints; +pub use self::endpoint::TripEndpoint; pub use self::external::{ExternalPerson, ExternalTrip, ExternalTripEndpoint, MapBorders}; pub use self::modifier::ScenarioModifier; pub use self::scenario::{IndividTrip, PersonSpec, Scenario, TripPurpose}; +mod endpoint; mod external; mod modifier; mod scenario; -/// Specifies where a trip begins or ends. -#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] -pub enum TripEndpoint { - Bldg(BuildingID), - Border(IntersectionID), - /// Used for interactive spawning, tests, etc. For now, only valid as a trip's start. - SuddenlyAppear(Position), -} - #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)] pub enum TripMode { Walk,