From ba2206d02a34e7bc0cb3e8603f754d50fef29478 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Tue, 19 Nov 2019 13:52:36 -0800 Subject: [PATCH] be able to start and end bike trips on bike lanes. use driving blackholes for now. --- docs/assumptions.md | 6 ++-- docs/project/references.md | 1 + game/src/sandbox/gameplay/spawner.rs | 8 ++--- game/src/sandbox/overlays.rs | 2 +- map_model/src/map.rs | 50 ++++++++++++++++++++++++++++ map_model/src/pathfind/driving.rs | 4 +-- map_model/src/traversable.rs | 13 ++++++++ sim/src/lib.rs | 14 +++++--- sim/src/make/spawner.rs | 15 +++++---- sim/src/trips.rs | 4 +-- 10 files changed, 95 insertions(+), 22 deletions(-) diff --git a/docs/assumptions.md b/docs/assumptions.md index fca34cccba..6be90096b4 100644 --- a/docs/assumptions.md +++ b/docs/assumptions.md @@ -33,9 +33,11 @@ will eventually shift mode or take different trips altogether. Not attempting any of that yet -- just using PSRC trips. I don't understand the demand modeling process well at all yet. -## Bikes using bus lanes +## Bike/bus lane connectivity -This should be pretty easy to allow. +Bikes and buses can make crazy left turns from the rightmost protected lane. +Alternatively, stop generating those turns and start generating turns between +protected and general lanes. ## Parking diff --git a/docs/project/references.md b/docs/project/references.md index ab664156b7..78e3219ac7 100644 --- a/docs/project/references.md +++ b/docs/project/references.md @@ -37,6 +37,7 @@ - https://www.the74million.org/article/building-a-smarter-and-cheaper-school-bus-system-how-a-boston-mit-partnership-led-to-new-routes-that-are-20-more-efficient-use-400-fewer-buses-save-5-million/ - https://www.citylab.com/perspective/2019/10/micromobility-urban-design-car-free-infrastruture-futurama/600163/ - https://www.sanjorn.com/ +- https://ui.kpf.com/ ## Similar projects diff --git a/game/src/sandbox/gameplay/spawner.rs b/game/src/sandbox/gameplay/spawner.rs index 1ce8e23d6c..9680351320 100644 --- a/game/src/sandbox/gameplay/spawner.rs +++ b/game/src/sandbox/gameplay/spawner.rs @@ -96,7 +96,8 @@ impl AgentSpawner { maybe_goal: None, })); } - // TODO First lane might be a bike lane! Need to pass PathConstraints. + } + if let Some(pos) = Position::bldg_via_biking(id, map) { if ctx .input .contextual_action(Key::F7, "spawn a bike starting here") @@ -198,8 +199,7 @@ impl State for AgentSpawner { if constraints == PathConstraints::Pedestrian { Position::bldg_via_walking(to, map) } else { - // TODO Specify biking maybe - DrivingGoal::ParkNear(to).goal_pos(map) + DrivingGoal::ParkNear(to).goal_pos(constraints, map) } } Goal::Border(to) => { @@ -208,7 +208,7 @@ impl State for AgentSpawner { constraints, map, ) { - g.goal_pos(map) + g.goal_pos(constraints, map) } else { self.maybe_goal = None; return Transition::Keep; diff --git a/game/src/sandbox/overlays.rs b/game/src/sandbox/overlays.rs index 11eb483097..a59aa8d482 100644 --- a/game/src/sandbox/overlays.rs +++ b/game/src/sandbox/overlays.rs @@ -404,7 +404,7 @@ fn calculate_bike_network(ctx: &EventCtx, ui: &UI) -> RoadColorer { fn calculate_bus_network(ctx: &EventCtx, ui: &UI) -> RoadColorer { let mut colorer = RoadColorerBuilder::new( Text::prompt("bus networks"), - vec![("bike lanes", Color::GREEN)], + vec![("bus lanes", Color::GREEN)], ); for l in ui.primary.map.all_lanes() { if l.is_bus() { diff --git a/map_model/src/map.rs b/map_model/src/map.rs index 45f9b57807..eaba03f3c5 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -531,6 +531,56 @@ impl Map { } } + // TODO Refactor and also use a different blackhole measure + pub fn find_biking_lane_near_building(&self, b: BuildingID) -> LaneID { + if let Ok(l) = self.find_closest_lane(self.get_b(b).sidewalk(), vec![LaneType::Biking]) { + return self.get_l(l).parking_blackhole.unwrap_or(l); + } + if let Ok(l) = self.find_closest_lane(self.get_b(b).sidewalk(), vec![LaneType::Driving]) { + return self.get_l(l).parking_blackhole.unwrap_or(l); + } + + let mut roads_queue: VecDeque = VecDeque::new(); + let mut visited: HashSet = HashSet::new(); + { + let start = self.building_to_road(b).id; + roads_queue.push_back(start); + visited.insert(start); + } + + loop { + if roads_queue.is_empty() { + panic!( + "Giving up looking for a biking or driving lane near {}, searched {} roads: {:?}", + b, + visited.len(), + visited + ); + } + let r = self.get_r(roads_queue.pop_front().unwrap()); + + for (lane, lane_type) in r + .children_forwards + .iter() + .chain(r.children_backwards.iter()) + { + if *lane_type == LaneType::Biking { + return self.get_l(*lane).parking_blackhole.unwrap_or(*lane); + } + if *lane_type == LaneType::Driving { + return self.get_l(*lane).parking_blackhole.unwrap_or(*lane); + } + } + + for next_r in self.get_next_roads(r.id).into_iter() { + if !visited.contains(&next_r) { + roads_queue.push_back(next_r); + visited.insert(next_r); + } + } + } + } + pub fn get_boundary_polygon(&self) -> &Polygon { &self.boundary_polygon } diff --git a/map_model/src/pathfind/driving.rs b/map_model/src/pathfind/driving.rs index fdf4cc322f..fb30cac782 100644 --- a/map_model/src/pathfind/driving.rs +++ b/map_model/src/pathfind/driving.rs @@ -79,7 +79,8 @@ impl VehiclePathfinder { req.end.dist_along(), Distance::centimeters(raw_path.get_weight()), ); - if self.constraints == PathConstraints::Bike { + // Disabled, because this looks stable now. + if false && self.constraints == PathConstraints::Bike { check_bike_route(&path, map); } Some(path) @@ -176,7 +177,6 @@ pub fn cost(lane: &Lane, turn: &Turn, constraints: PathConstraints, map: &Map) - } } -// TODO Temporary, while I'm figuring out why bike lanes aren't always used. fn check_bike_route(path: &Path, map: &Map) { let steps: Vec = path.get_steps().iter().cloned().collect(); for pair in steps.windows(2) { diff --git a/map_model/src/traversable.rs b/map_model/src/traversable.rs index 682eb525eb..bc88a2f674 100644 --- a/map_model/src/traversable.rs +++ b/map_model/src/traversable.rs @@ -72,6 +72,19 @@ impl Position { .equiv_pos(driving_lane, Distance::ZERO, map), ) } + + pub fn bldg_via_biking(b: BuildingID, map: &Map) -> Option { + let bldg = map.get_b(b); + let driving_lane = map + .find_closest_lane(bldg.sidewalk(), vec![LaneType::Biking]) + .or_else(|_| map.find_closest_lane(bldg.sidewalk(), vec![LaneType::Driving])) + .ok()?; + Some( + bldg.front_path + .sidewalk + .equiv_pos(driving_lane, Distance::ZERO, map), + ) + } } // TODO also building paths? diff --git a/sim/src/lib.rs b/sim/src/lib.rs index 265c42af61..c3e6794539 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -183,6 +183,8 @@ pub struct ParkedCar { pub spot: ParkingSpot, } +// It'd be nice to inline the goal_pos like SidewalkSpot does, but DrivingGoal is persisted in +// Scenarios, so this wouldn't survive map edits. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum DrivingGoal { ParkNear(BuildingID), @@ -204,17 +206,19 @@ impl DrivingGoal { } } - // TODO Stick this in the DrivingGoal directly, like SidewalkSpot. Find it upon construction. - pub fn goal_pos(&self, map: &Map) -> Position { + pub fn goal_pos(&self, constraints: PathConstraints, map: &Map) -> Position { let lane = match self { - // TODO Biking option. - DrivingGoal::ParkNear(b) => map.find_driving_lane_near_building(*b), + DrivingGoal::ParkNear(b) => match constraints { + PathConstraints::Car => map.find_driving_lane_near_building(*b), + PathConstraints::Bike => map.find_biking_lane_near_building(*b), + PathConstraints::Bus | PathConstraints::Pedestrian => unreachable!(), + }, DrivingGoal::Border(_, l) => *l, }; Position::new(lane, map.get_l(lane).length()) } - pub fn make_router(&self, path: Path, map: &Map, vt: VehicleType) -> Router { + pub(crate) fn make_router(&self, path: Path, map: &Map, vt: VehicleType) -> Router { match self { DrivingGoal::ParkNear(b) => { if vt == VehicleType::Bike { diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index edf35dd57d..c6b5cc1bf4 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -135,7 +135,7 @@ impl TripSpawner { return; } if let DrivingGoal::ParkNear(_) = goal { - let last_lane = goal.goal_pos(map).lane(); + let last_lane = goal.goal_pos(PathConstraints::Bike, map).lane(); // If bike_to_sidewalk works, then SidewalkSpot::bike_rack should too. if map .get_parent(last_lane) @@ -475,11 +475,14 @@ impl TripSpec { vehicle_spec, goal, .. - } => PathRequest { - start: *start_pos, - end: goal.goal_pos(map), - constraints: vehicle_spec.vehicle_type.to_constraints(), - }, + } => { + let constraints = vehicle_spec.vehicle_type.to_constraints(); + PathRequest { + start: *start_pos, + end: goal.goal_pos(constraints, map), + constraints, + } + } TripSpec::UsingParkedCar { start, spot, .. } => PathRequest { start: start.sidewalk_pos, end: SidewalkSpot::parking_spot(*spot, map, parking).sidewalk_pos, diff --git a/sim/src/trips.rs b/sim/src/trips.rs index 90cf599ea6..7e29fb0cbf 100644 --- a/sim/src/trips.rs +++ b/sim/src/trips.rs @@ -191,7 +191,7 @@ impl TripManager { // Actually, to unpark, the car's front should be where it'll wind up at the end. start = Position::new(start.lane(), start.dist_along() + parked_car.vehicle.length); } - let end = drive_to.goal_pos(map); + let end = drive_to.goal_pos(PathConstraints::Car, map); let path = if let Some(p) = map.pathfind(PathRequest { start, end, @@ -243,7 +243,7 @@ impl TripManager { _ => unreachable!(), }; - let end = drive_to.goal_pos(map); + let end = drive_to.goal_pos(PathConstraints::Bike, map); let path = if let Some(p) = map.pathfind(PathRequest { start: driving_pos, end,