defer choosing a starting lane for borders in scenarios, to respond to lane edits

This commit is contained in:
Dustin Carlino 2020-04-19 10:51:23 -07:00
parent 6d9695fb59
commit d80513235d
8 changed files with 177 additions and 207 deletions

View File

@ -219,15 +219,15 @@ d02d0d103f7b00672a5f1145c5169d8c data/system/fonts/Overpass-Bold.ttf
cc45f42cb24cad1cfdbf5ed7a0cb86d4 data/system/synthetic_maps/signal_double.json
8b949cc34d9a27ace0bd8ecde55a9520 data/system/synthetic_maps/signal_single.json
1cd7be125e1d992613ed3a41e8b25b6a data/system/synthetic_maps/signal_fan_in.json
dc7d7c8b8dec9c2d0375df3ed0c02d87 data/system/scenarios/ballard/weekday.bin
ec3d36603a91a1b61a63823404ad16d6 data/system/scenarios/intl_district/weekday.bin
34f366fc48867dc4d923fc638e804af0 data/system/scenarios/23rd/weekday.bin
bca018492766b536680a6aae9592d397 data/system/scenarios/downtown/weekday.bin
354a0d31dd19b31fe4599233a2057c41 data/system/scenarios/huge_seattle/weekday.bin
60712719b44a47cc6accf97d069fac92 data/system/scenarios/caphill/weekday.bin
8e38d4e4a93beb7080029dcb76d47563 data/system/scenarios/montlake/weekday.bin
ad5d2c7a2c73ef5bf4a09ff11845c166 data/system/prebaked_results/signal_single/tutorial lvl1.bin
81081122aa46e8651d8f07bb606bc77a data/system/prebaked_results/signal_single/tutorial lvl2.bin
e15dd2d01fccadb1f16c4ed9f732b377 data/system/prebaked_results/montlake/car vs bike contention.bin
1f1314220db40c778c18cd03e9af5d18 data/system/prebaked_results/montlake/weekday.bin
ba713f7f6ad0c0201f1447d77957ce75 data/system/prebaked_results/montlake/car vs bus contention.bin
1d447747aa2e493310abe5e103a1f693 data/system/scenarios/ballard/weekday.bin
a72c52c5493d3d7eeaec296ffc7025a2 data/system/scenarios/intl_district/weekday.bin
86603a31ee66b91ebb4a66e1146a3b17 data/system/scenarios/23rd/weekday.bin
17a1f28e4418937ff17504e357206b8c data/system/scenarios/downtown/weekday.bin
afb0ab6073cd6439ccaa41addd3de68b data/system/scenarios/huge_seattle/weekday.bin
248f2874b28dfda11cb052e53ef51414 data/system/scenarios/caphill/weekday.bin
bf0bef6566fb918db5c9ad3e9054731a data/system/scenarios/montlake/weekday.bin
3e4614071e27205513a5cc9b7fc11c90 data/system/prebaked_results/signal_single/tutorial lvl1.bin
eb732e116af8c64cd879e9acb1b2eb1f data/system/prebaked_results/signal_single/tutorial lvl2.bin
9577e253b4fa810edef68f3dd8860ff0 data/system/prebaked_results/montlake/car vs bike contention.bin
de9631bca160dea190cc23e5ac0b7139 data/system/prebaked_results/montlake/weekday.bin
b4a1a7f94385434dce3fb71b82e403e3 data/system/prebaked_results/montlake/car vs bus contention.bin

View File

@ -280,6 +280,14 @@ fn describe(person: &PersonSpec, trip: &IndividTrip, home: OD) -> String {
start.lane(),
driving_goal(goal)
),
SpawnTrip::FromBorder { i, goal, is_bike } => format!(
"{} at {}: {} appears at {}, goes to {}",
person.id,
trip.depart,
if *is_bike { "bike" } else { "car" },
i,
driving_goal(goal)
),
SpawnTrip::MaybeUsingParkedCar(start_bldg, goal) => format!(
"{} at {}: try to drive from {} to {}",
person.id,
@ -332,6 +340,7 @@ fn other_endpt(trip: &IndividTrip, home: OD, map: &Map) -> ID {
ID::Intersection(map.get_l(start.lane()).src_i),
driving_goal(goal),
),
SpawnTrip::FromBorder { i, goal, .. } => (ID::Intersection(*i), driving_goal(goal)),
SpawnTrip::MaybeUsingParkedCar(start_bldg, goal) => {
(ID::Building(*start_bldg), driving_goal(goal))
}
@ -445,6 +454,9 @@ impl DotMap {
SpawnTrip::CarAppearing { start, goal, .. } => {
(start.pt(map), goal.pt(map))
}
SpawnTrip::FromBorder { i, goal, .. } => {
(map.get_i(*i).polygon.center(), goal.pt(map))
}
SpawnTrip::MaybeUsingParkedCar(b, goal) => {
(map.get_b(*b).polygon.center(), goal.pt(map))
}

View File

@ -347,7 +347,7 @@ fn schedule_trip(
};
match src {
Source::Drive(from) => {
if let Some(start_pos) = TripSpec::spawn_car_at(*from, map) {
if let Some(start_pos) = TripSpec::spawn_vehicle_at(*from, false, map) {
spawner.schedule_trip(
sim.random_person(true),
sim.time(),

View File

@ -68,12 +68,18 @@ impl Intersection {
.collect()
}
// Strict for bikes. If there are bike lanes, not allowed to use other lanes.
pub fn get_outgoing_lanes(&self, map: &Map, constraints: PathConstraints) -> Vec<LaneID> {
self.outgoing_lanes
let mut choices: Vec<LaneID> = self
.outgoing_lanes
.iter()
.filter(|l| constraints.can_use(map.get_l(**l), map))
.cloned()
.collect()
.collect();
if constraints == PathConstraints::Bike {
choices.retain(|l| map.get_l(*l).is_biking());
}
choices
}
pub fn get_zorder(&self, map: &Map) -> isize {

View File

@ -2,10 +2,8 @@ use crate::psrc::{Endpoint, Mode, Parcel, Purpose};
use crate::PopDat;
use abstutil::{prettyprint_usize, MultiMap, Timer};
use geom::{Distance, Duration, LonLat, Polygon, Pt2D, Time};
use map_model::{BuildingID, IntersectionID, Map, PathConstraints, Position};
use sim::{
DrivingGoal, IndividTrip, PersonID, PersonSpec, Scenario, SidewalkSpot, SpawnTrip, TripSpec,
};
use map_model::{BuildingID, IntersectionID, Map, PathConstraints};
use sim::{DrivingGoal, IndividTrip, PersonID, PersonSpec, Scenario, SidewalkSpot, SpawnTrip};
use std::collections::HashMap;
#[derive(Clone, Debug)]
@ -37,72 +35,45 @@ impl Trip {
self.depart_at + self.trip_time
}
pub fn to_spawn_trip(&self, map: &Map) -> Option<SpawnTrip> {
fn to_spawn_trip(&self, map: &Map) -> SpawnTrip {
match self.mode {
Mode::Drive => match self.from {
TripEndpt::Border(i, _) => {
if let Some(start) = TripSpec::spawn_car_at(
Position::new(
map.get_i(i).get_outgoing_lanes(map, PathConstraints::Car)[0],
Distance::ZERO,
),
map,
) {
Some(SpawnTrip::CarAppearing {
start,
goal: self.to.driving_goal(PathConstraints::Car, map),
is_bike: false,
})
} else {
// TODO need to be able to emit warnings from parallelize
//timer.warn(format!("No room for car to appear at {:?}", self.from));
None
}
}
TripEndpt::Building(b) => Some(SpawnTrip::MaybeUsingParkedCar(
TripEndpt::Border(i, _) => SpawnTrip::FromBorder {
i,
goal: self.to.driving_goal(PathConstraints::Car, map),
is_bike: false,
},
TripEndpt::Building(b) => SpawnTrip::MaybeUsingParkedCar(
b,
self.to.driving_goal(PathConstraints::Car, map),
)),
),
},
Mode::Bike => match self.from {
TripEndpt::Building(b) => Some(SpawnTrip::UsingBike(
TripEndpt::Building(b) => SpawnTrip::UsingBike(
SidewalkSpot::building(b, map),
self.to.driving_goal(PathConstraints::Bike, map),
)),
TripEndpt::Border(i, _) => {
if let Some(start) = TripSpec::spawn_car_at(
Position::new(
map.get_i(i).get_outgoing_lanes(map, PathConstraints::Bike)[0],
Distance::ZERO,
),
map,
) {
Some(SpawnTrip::CarAppearing {
start,
goal: self.to.driving_goal(PathConstraints::Bike, map),
is_bike: true,
})
} else {
//timer.warn(format!("No room for bike to appear at {:?}", self.from));
None
}
}
),
TripEndpt::Border(i, _) => SpawnTrip::FromBorder {
i,
goal: self.to.driving_goal(PathConstraints::Bike, map),
is_bike: true,
},
},
Mode::Walk => Some(SpawnTrip::JustWalking(
Mode::Walk => SpawnTrip::JustWalking(
self.from.start_sidewalk_spot(map),
self.to.end_sidewalk_spot(map),
)),
),
Mode::Transit => {
let start = self.from.start_sidewalk_spot(map);
let goal = self.to.end_sidewalk_spot(map);
if let Some((stop1, stop2, route)) =
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
{
Some(SpawnTrip::UsingTransit(start, goal, route, stop1, stop2))
SpawnTrip::UsingTransit(start, goal, route, stop1, stop2)
} else {
//timer.warn(format!("{:?} not actually using transit, because pathfinding
// didn't find any useful route", trip));
Some(SpawnTrip::JustWalking(start, goal))
SpawnTrip::JustWalking(start, goal)
}
}
}
@ -289,13 +260,15 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
// person -> (trip seq, index into individ_trips)
let mut trips_per_person: MultiMap<(usize, usize), ((usize, bool, usize), usize)> =
MultiMap::new();
for (trip, depart, person, seq) in timer
.parallelize("turn PSRC trips into SpawnTrips", trips, |trip| {
trip.to_spawn_trip(map)
.map(|spawn| (spawn, trip.depart_at, trip.person, trip.seq))
for (trip, depart, person, seq) in
timer.parallelize("turn PSRC trips into SpawnTrips", trips, |trip| {
(
trip.to_spawn_trip(map),
trip.depart_at,
trip.person,
trip.seq,
)
})
.into_iter()
.flatten()
{
let idx = individ_trips.len();
individ_trips.push(Some(IndividTrip { depart, trip }));
@ -324,7 +297,7 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
let mut has_car = false;
for trip in &trips {
match trip.trip {
SpawnTrip::CarAppearing { is_bike, .. } => {
SpawnTrip::FromBorder { is_bike, .. } => {
if !is_bike {
has_car = true;
}

View File

@ -1,12 +1,7 @@
use crate::{
DrivingGoal, IndividTrip, PersonID, PersonSpec, Scenario, SidewalkSpot, SpawnTrip, BIKE_LENGTH,
MAX_CAR_LENGTH,
};
use crate::{DrivingGoal, IndividTrip, PersonID, PersonSpec, Scenario, SidewalkSpot, SpawnTrip};
use abstutil::Timer;
use geom::{Duration, Time};
use map_model::{
BuildingID, DirectedRoadID, FullNeighborhoodInfo, LaneID, Map, PathConstraints, Position,
};
use map_model::{BuildingID, DirectedRoadID, FullNeighborhoodInfo, Map, PathConstraints};
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
@ -80,8 +75,24 @@ impl ScenarioGenerator {
for s in &self.border_spawn_over_time {
timer.next();
s.spawn_peds(rng, &mut scenario, &neighborhoods, map, timer);
s.spawn_cars(rng, &mut scenario, &neighborhoods, map, timer);
s.spawn_bikes(rng, &mut scenario, &neighborhoods, map, timer);
s.spawn_vehicles(
s.num_cars,
PathConstraints::Car,
rng,
&mut scenario,
&neighborhoods,
map,
timer,
);
s.spawn_vehicles(
s.num_bikes,
PathConstraints::Bike,
rng,
&mut scenario,
&neighborhoods,
map,
timer,
);
}
timer.stop(format!("Generating scenario {}", self.scenario_name));
@ -327,97 +338,34 @@ impl BorderSpawnOverTime {
}
}
fn spawn_cars(
fn spawn_vehicles(
&self,
num: usize,
constraints: PathConstraints,
rng: &mut XorShiftRng,
scenario: &mut Scenario,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
if self.num_cars == 0 {
return;
}
let lanes = pick_starting_lanes(
self.start_from_border.lanes(PathConstraints::Car, map),
false,
map,
);
if lanes.is_empty() {
timer.warn(format!(
"Can't start {} cars at border for {}",
self.num_cars, self.start_from_border
));
return;
};
for _ in 0..self.num_cars {
for _ in 0..num {
let depart = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Car, map, &neighborhoods, rng, timer)
.pick_driving_goal(constraints, map, &neighborhoods, rng, timer)
{
let id = PersonID(scenario.people.len());
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::CarAppearing {
// Safe because pick_starting_lanes checks for this
start: Position::new(*lanes.choose(rng).unwrap(), MAX_CAR_LENGTH),
trip: SpawnTrip::FromBorder {
i: self.start_from_border.src_i(map),
goal,
is_bike: false,
is_bike: constraints == PathConstraints::Bike,
},
}],
has_car: true,
car_initially_parked_at: None,
});
}
}
}
fn spawn_bikes(
&self,
rng: &mut XorShiftRng,
scenario: &mut Scenario,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
if self.num_bikes == 0 {
return;
}
let lanes = pick_starting_lanes(
self.start_from_border.lanes(PathConstraints::Bike, map),
true,
map,
);
if lanes.is_empty() {
timer.warn(format!(
"Can't start {} bikes at border for {}",
self.num_bikes, self.start_from_border
));
return;
};
for _ in 0..self.num_bikes {
let depart = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Bike, map, &neighborhoods, rng, timer)
{
let id = PersonID(scenario.people.len());
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::CarAppearing {
start: Position::new(*lanes.choose(rng).unwrap(), BIKE_LENGTH),
goal,
is_bike: true,
},
}],
has_car: false,
has_car: constraints == PathConstraints::Car,
car_initially_parked_at: None,
});
}
@ -487,22 +435,3 @@ fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time {
assert!(high > low);
Time::START_OF_DAY + Duration::seconds(rng.gen_range(low.inner_seconds(), high.inner_seconds()))
}
fn pick_starting_lanes(mut lanes: Vec<LaneID>, is_bike: bool, map: &Map) -> Vec<LaneID> {
let min_len = if is_bike { BIKE_LENGTH } else { MAX_CAR_LENGTH };
lanes.retain(|l| map.get_l(*l).length() > min_len);
if is_bike {
// If there's a choice between bike lanes and otherwise, always use the bike lanes.
let bike_lanes = lanes
.iter()
.filter(|l| map.get_l(**l).is_biking())
.cloned()
.collect::<Vec<LaneID>>();
if !bike_lanes.is_empty() {
lanes = bike_lanes;
}
}
lanes
}

View File

@ -4,7 +4,9 @@ use crate::{
};
use abstutil::{MultiMap, Timer};
use geom::{Distance, Duration, Speed, Time};
use map_model::{BuildingID, BusRouteID, BusStopID, IntersectionID, Map, Position, RoadID};
use map_model::{
BuildingID, BusRouteID, BusStopID, IntersectionID, Map, PathConstraints, Position, RoadID,
};
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
@ -39,11 +41,16 @@ pub struct IndividTrip {
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum SpawnTrip {
// Only for interactive / debug trips
CarAppearing {
// TODO Replace start with building|border
start: Position,
goal: DrivingGoal,
// For bikes starting at a border, use CarAppearing. UsingBike implies a walk->bike trip.
is_bike: bool,
},
FromBorder {
i: IntersectionID,
goal: DrivingGoal,
// For bikes starting at a border, use FromBorder. UsingBike implies a walk->bike trip.
is_bike: bool,
},
MaybeUsingParkedCar(BuildingID, DrivingGoal),
@ -90,9 +97,14 @@ impl Scenario {
// TODO Or spawner?
sim.new_person(p.id, p.has_car);
for t in &p.trips {
// The RNG call is stable over edits.
let spec = t.trip.clone().to_trip_spec(rng);
spawner.schedule_trip(p.id, t.depart, spec, map, sim);
// The RNG call might change over edits for picking the spawning lane from a border
// with multiple choices for a vehicle type.
let mut tmp_rng = abstutil::fork_rng(rng);
if let Some(spec) = t.trip.clone().to_trip_spec(&mut tmp_rng, map) {
spawner.schedule_trip(p.id, t.depart, spec, map, sim);
} else {
timer.warn(format!("Couldn't turn {:?} into a trip", t.trip));
}
}
}
@ -294,14 +306,14 @@ fn find_spot_near_building(
}
impl SpawnTrip {
fn to_trip_spec(self, rng: &mut XorShiftRng) -> TripSpec {
fn to_trip_spec(self, rng: &mut XorShiftRng, map: &Map) -> Option<TripSpec> {
match self {
SpawnTrip::CarAppearing {
start,
goal,
is_bike,
..
} => TripSpec::CarAppearing {
} => Some(TripSpec::CarAppearing {
start_pos: start,
goal,
vehicle_spec: if is_bike {
@ -310,37 +322,67 @@ impl SpawnTrip {
Scenario::rand_car(rng)
},
ped_speed: Scenario::rand_ped_speed(rng),
},
SpawnTrip::MaybeUsingParkedCar(start_bldg, goal) => TripSpec::MaybeUsingParkedCar {
start_bldg,
}),
SpawnTrip::FromBorder {
i, goal, is_bike, ..
} => Some(TripSpec::CarAppearing {
start_pos: {
let l = *map
.get_i(i)
.get_outgoing_lanes(
map,
if is_bike {
PathConstraints::Bike
} else {
PathConstraints::Car
},
)
.choose(rng)?;
TripSpec::spawn_vehicle_at(Position::new(l, Distance::ZERO), is_bike, map)?
},
goal,
vehicle_spec: if is_bike {
Scenario::rand_bike(rng)
} else {
Scenario::rand_car(rng)
},
ped_speed: Scenario::rand_ped_speed(rng),
},
SpawnTrip::UsingBike(start, goal) => TripSpec::UsingBike {
}),
SpawnTrip::MaybeUsingParkedCar(start_bldg, goal) => {
Some(TripSpec::MaybeUsingParkedCar {
start_bldg,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
})
}
SpawnTrip::UsingBike(start, goal) => Some(TripSpec::UsingBike {
start,
goal,
vehicle: Scenario::rand_bike(rng),
ped_speed: Scenario::rand_ped_speed(rng),
},
SpawnTrip::JustWalking(start, goal) => TripSpec::JustWalking {
}),
SpawnTrip::JustWalking(start, goal) => Some(TripSpec::JustWalking {
start,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
SpawnTrip::UsingTransit(start, goal, route, stop1, stop2) => TripSpec::UsingTransit {
start,
goal,
route,
stop1,
stop2,
ped_speed: Scenario::rand_ped_speed(rng),
},
}),
SpawnTrip::UsingTransit(start, goal, route, stop1, stop2) => {
Some(TripSpec::UsingTransit {
start,
goal,
route,
stop1,
stop2,
ped_speed: Scenario::rand_ped_speed(rng),
})
}
}
}
pub fn start_from_bldg(&self) -> Option<BuildingID> {
match self {
SpawnTrip::CarAppearing { .. } => None,
SpawnTrip::FromBorder { .. } => None,
SpawnTrip::MaybeUsingParkedCar(b, _) => Some(*b),
SpawnTrip::UsingBike(ref spot, _)
| SpawnTrip::JustWalking(ref spot, _)
@ -353,8 +395,8 @@ impl SpawnTrip {
pub fn start_from_border(&self) -> Option<IntersectionID> {
match self {
// TODO CarAppearing might be from a border
SpawnTrip::CarAppearing { .. } => None,
SpawnTrip::FromBorder { i, .. } => Some(*i),
SpawnTrip::MaybeUsingParkedCar(_, _) => None,
SpawnTrip::UsingBike(ref spot, _)
| SpawnTrip::JustWalking(ref spot, _)
@ -368,6 +410,7 @@ impl SpawnTrip {
pub fn end_at_bldg(&self) -> Option<BuildingID> {
match self {
SpawnTrip::CarAppearing { ref goal, .. }
| SpawnTrip::FromBorder { ref goal, .. }
| SpawnTrip::MaybeUsingParkedCar(_, ref goal)
| SpawnTrip::UsingBike(_, ref goal) => match goal {
DrivingGoal::ParkNear(b) => Some(*b),
@ -385,6 +428,7 @@ impl SpawnTrip {
pub fn end_at_border(&self) -> Option<IntersectionID> {
match self {
SpawnTrip::CarAppearing { ref goal, .. }
| SpawnTrip::FromBorder { ref goal, .. }
| SpawnTrip::MaybeUsingParkedCar(_, ref goal)
| SpawnTrip::UsingBike(_, ref goal) => match goal {
DrivingGoal::ParkNear(_) => None,

View File

@ -1,7 +1,7 @@
use crate::{
CarID, Command, CreateCar, CreatePedestrian, DrivingGoal, ParkingSimState, ParkingSpot,
PedestrianID, PersonID, Scheduler, SidewalkPOI, SidewalkSpot, Sim, TripEndpoint, TripLeg,
TripManager, VehicleSpec, VehicleType, MAX_CAR_LENGTH,
TripManager, VehicleSpec, VehicleType, BIKE_LENGTH, MAX_CAR_LENGTH,
};
use abstutil::Timer;
use geom::{Speed, Time, EPSILON_DIST};
@ -138,13 +138,15 @@ impl TripSpawner {
} => {
if start_pos.dist_along() < vehicle_spec.length {
panic!(
"Can't spawn a car at {}; too close to the start",
"Can't spawn a {:?} at {}; too close to the start",
vehicle_spec.vehicle_type,
start_pos.dist_along()
);
}
if start_pos.dist_along() >= map.get_l(start_pos.lane()).length() {
panic!(
"Can't spawn a car at {}; {} isn't that long",
"Can't spawn a {:?} at {}; {} isn't that long",
vehicle_spec.vehicle_type,
start_pos.dist_along(),
start_pos.lane()
);
@ -154,7 +156,10 @@ impl TripSpawner {
if start_pos.lane() == *end_lane
&& start_pos.dist_along() == map.get_l(*end_lane).length()
{
panic!("Can't start a car at the edge of a border already");
panic!(
"Can't start a {:?} at the edge of a border already",
vehicle_spec.vehicle_type
);
}
}
DrivingGoal::ParkNear(_) => {}
@ -534,16 +539,17 @@ impl TripSpawner {
impl TripSpec {
// If possible, fixes problems that schedule_trip would hit.
pub fn spawn_car_at(pos: Position, map: &Map) -> Option<Position> {
let len = map.get_l(pos.lane()).length();
pub fn spawn_vehicle_at(pos: Position, is_bike: bool, map: &Map) -> Option<Position> {
let lane_len = map.get_l(pos.lane()).length();
let vehicle_len = if is_bike { BIKE_LENGTH } else { MAX_CAR_LENGTH };
// There's no hope.
if len <= MAX_CAR_LENGTH {
if lane_len <= vehicle_len {
return None;
}
if pos.dist_along() < MAX_CAR_LENGTH {
Some(Position::new(pos.lane(), MAX_CAR_LENGTH))
} else if pos.dist_along() == len {
if pos.dist_along() < vehicle_len {
Some(Position::new(pos.lane(), vehicle_len))
} else if pos.dist_along() == lane_len {
Some(Position::new(pos.lane(), pos.dist_along() - EPSILON_DIST))
} else {
Some(pos)