use rand::seq::SliceRandom;
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize};
use map_model::{BuildingID, BusRouteID, BusStopID, Map, PathConstraints, PathRequest, Position};
use crate::{
CarID, DrivingGoal, PersonID, SidewalkSpot, TripEndpoint, TripInfo, TripLeg, TripMode,
VehicleType, SPAWN_DIST,
};
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub(crate) enum TripSpec {
VehicleAppearing {
start_pos: Position,
goal: DrivingGoal,
use_vehicle: CarID,
retry_if_no_room: bool,
},
SpawningFailure {
use_vehicle: Option<CarID>,
error: String,
},
UsingParkedCar {
car: CarID,
start_bldg: BuildingID,
goal: DrivingGoal,
},
JustWalking {
start: SidewalkSpot,
goal: SidewalkSpot,
},
UsingBike {
bike: CarID,
start: BuildingID,
goal: DrivingGoal,
},
UsingTransit {
start: SidewalkSpot,
goal: SidewalkSpot,
route: BusRouteID,
stop1: BusStopID,
maybe_stop2: Option<BusStopID>,
},
}
impl TripSpec {
pub fn to_plan(
self,
person: PersonID,
info: TripInfo,
map: &Map,
) -> (PersonID, TripInfo, TripSpec, Vec<TripLeg>) {
let mut legs = Vec::new();
match &self {
TripSpec::VehicleAppearing {
start_pos,
goal,
use_vehicle,
..
} => {
if start_pos.dist_along() >= map.get_l(start_pos.lane()).length() {
panic!("Can't spawn at {}; it isn't that long", start_pos);
}
if let DrivingGoal::Border(_, end_lane) = goal {
if start_pos.lane() == *end_lane
&& start_pos.dist_along() == map.get_l(*end_lane).length()
{
panic!(
"Can't start at {}; it's the edge of a border already",
start_pos
);
}
}
let constraints = if use_vehicle.1 == VehicleType::Bike {
PathConstraints::Bike
} else {
PathConstraints::Car
};
legs.push(TripLeg::Drive(*use_vehicle, goal.clone()));
if let DrivingGoal::ParkNear(b) = goal {
legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
}
if goal.goal_pos(constraints, map).is_none() {
return TripSpec::SpawningFailure {
use_vehicle: Some(use_vehicle.clone()),
error: format!("goal_pos to {:?} for a {:?} failed", goal, constraints),
}
.to_plan(person, info, map);
}
}
TripSpec::SpawningFailure { .. } => {
legs.push(TripLeg::RideBus(BusRouteID(0), None));
}
TripSpec::UsingParkedCar { car, goal, .. } => {
legs.push(TripLeg::Walk(SidewalkSpot::deferred_parking_spot()));
legs.push(TripLeg::Drive(*car, goal.clone()));
match goal {
DrivingGoal::ParkNear(b) => {
legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
}
DrivingGoal::Border(_, _) => {}
}
}
TripSpec::JustWalking { start, goal, .. } => {
if start == goal {
panic!(
"A trip just walking from {:?} to {:?} doesn't make sense",
start, goal
);
}
legs.push(TripLeg::Walk(goal.clone()));
}
TripSpec::UsingBike { start, goal, bike } => {
let backup_plan = match goal {
DrivingGoal::ParkNear(b) => Some(TripSpec::JustWalking {
start: SidewalkSpot::building(*start, map),
goal: SidewalkSpot::building(*b, map),
}),
DrivingGoal::Border(i, _) => {
SidewalkSpot::end_at_border(*i, map).map(|goal| TripSpec::JustWalking {
start: SidewalkSpot::building(*start, map),
goal,
})
}
};
if let Some(start_spot) = SidewalkSpot::bike_rack(*start, map) {
if let DrivingGoal::ParkNear(b) = goal {
if let Some(goal_spot) = SidewalkSpot::bike_rack(*b, map) {
if start_spot.sidewalk_pos.lane() == goal_spot.sidewalk_pos.lane() {
info!(
"Bike trip from {} to {} will just walk; it's the same \
sidewalk!",
start, b
);
return backup_plan.unwrap().to_plan(person, info, map);
}
} else {
info!(
"Can't find biking connection for goal {}, walking instead",
b
);
return backup_plan.unwrap().to_plan(person, info, map);
}
}
legs.push(TripLeg::Walk(start_spot));
legs.push(TripLeg::Drive(*bike, goal.clone()));
match goal {
DrivingGoal::ParkNear(b) => {
legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
}
DrivingGoal::Border(_, _) => {}
}
} else if backup_plan.is_some() {
info!("Can't start biking from {}. Walking instead", start);
return backup_plan.unwrap().to_plan(person, info, map);
} else {
return TripSpec::SpawningFailure {
use_vehicle: Some(*bike),
error: format!(
"Can't start biking from {} and can't walk either! Goal is {:?}",
start, goal
),
}
.to_plan(person, info, map);
}
}
TripSpec::UsingTransit {
route,
stop1,
maybe_stop2,
goal,
..
} => {
let walk_to = SidewalkSpot::bus_stop(*stop1, map);
if let Some(stop2) = maybe_stop2 {
legs = vec![
TripLeg::Walk(walk_to.clone()),
TripLeg::RideBus(*route, Some(*stop2)),
TripLeg::Walk(goal.clone()),
];
} else {
legs = vec![
TripLeg::Walk(walk_to.clone()),
TripLeg::RideBus(*route, None),
];
}
}
};
(person, info, self, legs)
}
pub fn get_pathfinding_request(&self, map: &Map) -> Option<PathRequest> {
match self {
TripSpec::VehicleAppearing {
start_pos,
goal,
use_vehicle,
..
} => {
let constraints = if use_vehicle.1 == VehicleType::Bike {
PathConstraints::Bike
} else {
PathConstraints::Car
};
Some(PathRequest {
start: *start_pos,
end: goal.goal_pos(constraints, map).unwrap(),
constraints,
})
}
TripSpec::SpawningFailure { .. } => None,
TripSpec::UsingParkedCar { .. } => None,
TripSpec::JustWalking { start, goal, .. } => Some(PathRequest {
start: start.sidewalk_pos,
end: goal.sidewalk_pos,
constraints: PathConstraints::Pedestrian,
}),
TripSpec::UsingBike { start, .. } => Some(PathRequest {
start: map.get_b(*start).sidewalk_pos,
end: SidewalkSpot::bike_rack(*start, map).unwrap().sidewalk_pos,
constraints: PathConstraints::Pedestrian,
}),
TripSpec::UsingTransit { start, stop1, .. } => Some(PathRequest {
start: start.sidewalk_pos,
end: SidewalkSpot::bus_stop(*stop1, map).sidewalk_pos,
constraints: PathConstraints::Pedestrian,
}),
}
}
pub fn maybe_new(
from: TripEndpoint,
to: TripEndpoint,
mode: TripMode,
use_vehicle: Option<CarID>,
retry_if_no_room: bool,
rng: &mut XorShiftRng,
map: &Map,
) -> Result<TripSpec, String> {
Ok(match mode {
TripMode::Drive | TripMode::Bike => {
let constraints = if mode == TripMode::Drive {
PathConstraints::Car
} else {
PathConstraints::Bike
};
let goal = to.driving_goal(constraints, map)?;
match from {
TripEndpoint::Bldg(start_bldg) => {
if mode == TripMode::Drive {
TripSpec::UsingParkedCar {
start_bldg,
goal,
car: use_vehicle.unwrap(),
}
} else {
TripSpec::UsingBike {
start: start_bldg,
goal,
bike: use_vehicle.unwrap(),
}
}
}
TripEndpoint::Border(i) => {
let start_lane = map
.get_i(i)
.some_outgoing_road(map)
.and_then(|dr| dr.lanes(constraints, map).choose(rng).cloned())
.ok_or_else(|| {
format!("can't start a {} trip from {}", mode.ongoing_verb(), i)
})?;
TripSpec::VehicleAppearing {
start_pos: Position::new(start_lane, SPAWN_DIST),
goal,
use_vehicle: use_vehicle.unwrap(),
retry_if_no_room,
}
}
TripEndpoint::SuddenlyAppear(start_pos) => TripSpec::VehicleAppearing {
start_pos,
goal,
use_vehicle: use_vehicle.unwrap(),
retry_if_no_room,
},
}
}
TripMode::Walk => TripSpec::JustWalking {
start: from.start_sidewalk_spot(map)?,
goal: to.end_sidewalk_spot(map)?,
},
TripMode::Transit => {
let start = from.start_sidewalk_spot(map)?;
let goal = to.end_sidewalk_spot(map)?;
if let Some((stop1, maybe_stop2, route)) =
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
{
TripSpec::UsingTransit {
start,
goal,
route,
stop1,
maybe_stop2,
}
} else {
TripSpec::JustWalking { start, goal }
}
}
})
}
}
impl TripEndpoint {
fn start_sidewalk_spot(&self, map: &Map) -> Result<SidewalkSpot, String> {
match self {
TripEndpoint::Bldg(b) => Ok(SidewalkSpot::building(*b, map)),
TripEndpoint::Border(i) => SidewalkSpot::start_at_border(*i, map)
.ok_or_else(|| format!("can't start walking from {}", i)),
TripEndpoint::SuddenlyAppear(pos) => Ok(SidewalkSpot::suddenly_appear(*pos, map)),
}
}
fn end_sidewalk_spot(&self, map: &Map) -> Result<SidewalkSpot, String> {
match self {
TripEndpoint::Bldg(b) => Ok(SidewalkSpot::building(*b, map)),
TripEndpoint::Border(i) => SidewalkSpot::end_at_border(*i, map)
.ok_or_else(|| format!("can't end walking at {}", i)),
TripEndpoint::SuddenlyAppear(_) => unreachable!(),
}
}
fn driving_goal(&self, constraints: PathConstraints, map: &Map) -> Result<DrivingGoal, String> {
match self {
TripEndpoint::Bldg(b) => Ok(DrivingGoal::ParkNear(*b)),
TripEndpoint::Border(i) => map
.get_i(*i)
.some_incoming_road(map)
.and_then(|dr| DrivingGoal::end_at_border(dr, constraints, map))
.ok_or_else(|| format!("can't end at {} for {:?}", i, constraints)),
TripEndpoint::SuddenlyAppear(_) => unreachable!(),
}
}
}