From 5c506f726cf4c27ed719472f46e249a7ec44725e Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 31 Jul 2020 14:28:40 -0700 Subject: [PATCH] total overhaul to building<->bike connections for #221 and #176. bikes will start/stop directly in front of a building driveway, when possible. still need to handle the case when the bikeable position isn't connected to most to the graph (for buildings accessible only by footway and for things around the border) --- map_model/src/map.rs | 51 ------------------------- map_model/src/objects/building.rs | 54 ++++++++++++++++++++++++++- map_model/src/objects/road.rs | 60 ++++++++++++++++++------------ sim/src/lib.rs | 62 +++++++------------------------ sim/src/make/spawner.rs | 62 ++++--------------------------- sim/src/mechanics/intersection.rs | 2 +- sim/src/router.rs | 43 +++++---------------- sim/src/trips.rs | 15 +++----- 8 files changed, 127 insertions(+), 222 deletions(-) diff --git a/map_model/src/map.rs b/map_model/src/map.rs index fd70850734..d4e10ff8db 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -509,57 +509,6 @@ 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/objects/building.rs b/map_model/src/objects/building.rs index 02472e154e..db7301f7be 100644 --- a/map_model/src/objects/building.rs +++ b/map_model/src/objects/building.rs @@ -1,8 +1,8 @@ -use crate::{LaneID, LaneType, Map, Position}; +use crate::{LaneID, LaneType, Map, PathConstraints, Position}; use abstutil::{deserialize_usize, serialize_usize}; use geom::{Distance, PolyLine, Polygon, Pt2D}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashSet, VecDeque}; use std::fmt; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] @@ -84,6 +84,7 @@ impl Building { } // The polyline goes from the building to the driving position + // TODO Make this handle parking_blackhole pub fn driving_connection(&self, map: &Map) -> Option<(Position, PolyLine)> { // Is there even a driving lane on the same side as our sidewalk? // TODO Handle offside @@ -101,6 +102,40 @@ impl Building { Some((pos, self.driveway_geom.clone().must_push(pos.pt(map)))) } + // Returns (biking position, sidewalk position) + pub fn biking_connection(&self, map: &Map) -> (Position, Position) { + // Easy case: the building is directly next to a usable lane + if let Some(pair) = sidewalk_to_bike(self.sidewalk_pos, map) { + return pair; + } + + // Floodfill the sidewalk graph until we find a sidewalk<->bike connection. + let mut queue: VecDeque = VecDeque::new(); + let mut visited: HashSet = HashSet::new(); + queue.push_back(self.sidewalk()); + + loop { + if queue.is_empty() { + panic!("Giving up looking for a biking_connection near {}", self.id); + } + let l = queue.pop_front().unwrap(); + if visited.contains(&l) { + continue; + } + visited.insert(l); + // TODO Could search by sidewalk endpoint + if let Some(pair) = sidewalk_to_bike(Position::new(l, map.get_l(l).length() / 2.0), map) + { + return pair; + } + for t in map.get_turns_from_lane(l) { + if !visited.contains(&t.id.dst) { + queue.push_back(t.id.dst); + } + } + } + } + pub fn num_parking_spots(&self) -> usize { match self.parking { OffstreetParking::PublicGarage(_, n) => n, @@ -108,3 +143,18 @@ impl Building { } } } + +// TODO Maybe we should also handle blackhole (but to be very careful, in a biking graph) +fn sidewalk_to_bike(sidewalk_pos: Position, map: &Map) -> Option<(Position, Position)> { + let lane = map.get_parent(sidewalk_pos.lane()).find_closest_lane_v2( + sidewalk_pos.lane(), + true, + |l| PathConstraints::Bike.can_use(l, map), + map, + )?; + // No buffer needed + Some(( + sidewalk_pos.equiv_pos(lane, Distance::ZERO, map), + sidewalk_pos, + )) +} diff --git a/map_model/src/objects/road.rs b/map_model/src/objects/road.rs index 04032a5889..fc1c615468 100644 --- a/map_model/src/objects/road.rs +++ b/map_model/src/objects/road.rs @@ -1,5 +1,5 @@ use crate::raw::{OriginalRoad, RestrictionType}; -use crate::{osm, BusStopID, IntersectionID, LaneID, LaneType, Map, PathConstraints, Zone}; +use crate::{osm, BusStopID, IntersectionID, Lane, LaneID, LaneType, Map, PathConstraints, Zone}; use abstutil::{deserialize_usize, serialize_usize, Tags}; use enumset::EnumSet; use geom::{Distance, PolyLine, Polygon, Speed}; @@ -170,28 +170,6 @@ impl Road { .map(|(id, _)| *id) } - pub fn sidewalk_to_bike(&self, sidewalk: LaneID) -> Option { - // TODO Crossing bus lanes means higher layers of sim should know to block these off - // Oneways mean we might need to consider the other side of the road. - let (fwds, idx) = self.dir_and_offset(sidewalk); - self.children(fwds)[0..idx] - .iter() - .rev() - .chain(self.children(!fwds).iter()) - // TODO Bug, bus lanes OK. use PathConstraints::Bike. - .find(|(_, lt)| *lt == LaneType::Driving || *lt == LaneType::Biking) - .map(|(id, _)| *id) - } - - pub fn bike_to_sidewalk(&self, bike: LaneID) -> Option { - // TODO Crossing bus lanes means higher layers of sim should know to block these off - let (fwds, idx) = self.dir_and_offset(bike); - self.children(fwds)[idx..] - .iter() - .find(|(_, lt)| *lt == LaneType::Sidewalk || *lt == LaneType::Shoulder) - .map(|(id, _)| *id) - } - pub(crate) fn speed_limit_from_osm(&self) -> Speed { if let Some(limit) = self.osm_tags.get(osm::MAXSPEED) { if let Ok(kmph) = limit.parse::() { @@ -271,6 +249,42 @@ impl Road { } } + // TODO Migrate and rip out all the old stuff + pub(crate) fn find_closest_lane_v2 bool>( + &self, + from: LaneID, + include_offside: bool, + filter: F, + map: &Map, + ) -> Option { + // (lane, direction) from left to right over the whole road. I suspect children will + // eventually just be this. + let mut all: Vec<(LaneID, bool)> = Vec::new(); + for (l, _) in self.children_backwards.iter().rev() { + all.push((*l, false)); + } + for (l, _) in &self.children_forwards { + all.push((*l, true)); + } + let our_idx = all.iter().position(|(l, _)| *l == from).unwrap() as isize; + + let (fwd, _) = self.dir_and_offset(from); + all.into_iter() + .enumerate() + .filter_map(|(idx, (l, dir))| { + if (idx as isize) != our_idx + && (dir == fwd || include_offside) + && filter(map.get_l(l)) + { + Some((idx, l)) + } else { + None + } + }) + .min_by_key(|(idx, _)| (our_idx - (*idx as isize)).abs()) + .map(|(_, l)| l) + } + pub fn all_lanes(&self) -> Vec { self.children_forwards .iter() diff --git a/sim/src/lib.rs b/sim/src/lib.rs index b7254a1818..64bc30a42e 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -332,10 +332,7 @@ impl DrivingGoal { match self { DrivingGoal::ParkNear(b) => match constraints { PathConstraints::Car => Position::start(map.find_driving_lane_near_building(*b)), - PathConstraints::Bike => { - let l = map.find_biking_lane_near_building(*b); - Position::new(l, map.get_l(l).length() / 2.0) - } + PathConstraints::Bike => map.get_b(*b).biking_connection(map).0, PathConstraints::Bus | PathConstraints::Train | PathConstraints::Pedestrian => { unreachable!() } @@ -344,24 +341,18 @@ impl DrivingGoal { } } - // Only possible failure is if there's not a way to go bike->sidewalk at the end - pub(crate) fn make_router(&self, owner: CarID, path: Path, map: &Map) -> Option { + pub(crate) fn make_router(&self, owner: CarID, path: Path, map: &Map) -> Router { match self { DrivingGoal::ParkNear(b) => { if owner.1 == VehicleType::Bike { - // TODO Stop closer to the building? - let end = path.last_step().as_lane(); - Router::bike_then_stop(owner, path, map.get_l(end).length() / 2.0, map) + Router::bike_then_stop(owner, path, SidewalkSpot::bike_rack(*b, map)) } else { - Some(Router::park_near(owner, path, *b)) + Router::park_near(owner, path, *b) } } - DrivingGoal::Border(i, last_lane, _) => Some(Router::end_at_border( - owner, - path, - map.get_l(*last_lane).length(), - *i, - )), + DrivingGoal::Border(i, last_lane, _) => { + Router::end_at_border(owner, path, map.get_l(*last_lane).length(), *i) + } } } @@ -389,7 +380,7 @@ pub enum SidewalkPOI { Building(BuildingID), BusStop(BusStopID), Border(IntersectionID, Option), - // The equivalent position on the nearest driving/bike lane + // The bikeable position BikeRack(Position), SuddenlyAppear, } @@ -422,38 +413,13 @@ impl SidewalkSpot { } } - pub fn bike_rack(sidewalk: LaneID, map: &Map) -> Option { - assert!(map.get_l(sidewalk).is_walkable()); - let driving_lane = map.get_parent(sidewalk).sidewalk_to_bike(sidewalk)?; - // TODO Arbitrary, but safe - let sidewalk_pos = Position::new(sidewalk, map.get_l(sidewalk).length() / 2.0); - let driving_pos = sidewalk_pos.equiv_pos(driving_lane, Distance::ZERO, map); - Some(SidewalkSpot { - connection: SidewalkPOI::BikeRack(driving_pos), + // TODO For the case when we have to start/stop biking somewhere else, this won't match up with + // a building though! + pub fn bike_rack(b: BuildingID, map: &Map) -> SidewalkSpot { + let (bike_pos, sidewalk_pos) = map.get_b(b).biking_connection(map); + SidewalkSpot { + connection: SidewalkPOI::BikeRack(bike_pos), sidewalk_pos, - }) - } - - pub fn bike_from_bike_rack(sidewalk: LaneID, map: &Map) -> Option { - assert!(map.get_l(sidewalk).is_walkable()); - let driving_lane = map.get_parent(sidewalk).sidewalk_to_bike(sidewalk)?; - // Don't start biking on a blackhole! - // TODO Maybe compute a separate blackhole graph that includes bike lanes. - if let Some(redirect) = map.get_l(driving_lane).parking_blackhole { - // Make sure the driving lane is at least long enough to spawn on. Bikes spawn in the - // middle, so it needs to be double. - if map.get_l(redirect).length() < 2.0 * BIKE_LENGTH { - return None; - } - - let new_sidewalk = map.get_parent(redirect).bike_to_sidewalk(redirect)?; - SidewalkSpot::bike_rack(new_sidewalk, map) - } else { - if map.get_l(driving_lane).length() < 2.0 * BIKE_LENGTH { - return None; - } - - SidewalkSpot::bike_rack(sidewalk, map) } } diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index 492cc4b6dd..3611c0cd84 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -114,58 +114,16 @@ impl TripSpawner { } } TripSpec::UsingBike { start, goal, .. } => { - // TODO Might not be possible to walk to the same border if there's no sidewalk - let backup_plan = match goal { - DrivingGoal::ParkNear(b) => Some(TripSpec::JustWalking { - start: SidewalkSpot::building(*start, map), - goal: SidewalkSpot::building(*b, map), - }), - DrivingGoal::Border(i, _, off_map) => { - SidewalkSpot::end_at_border(*i, off_map.clone(), map).map(|goal| { - TripSpec::JustWalking { - start: SidewalkSpot::building(*start, map), - goal, - } - }) - } - }; - - if SidewalkSpot::bike_from_bike_rack(map.get_b(*start).sidewalk(), map).is_none() { - if backup_plan.is_some() { - println!( - "Can't start biking from {}; no biking or driving lane nearby? \ - Walking instead", - start - ); - spec = backup_plan.unwrap(); - } else { - panic!( - "Can't start biking from {}; no biking or driving lane nearby? Can't \ - walk instead, goal is {:?}", - start, goal - ); - } - } else if let DrivingGoal::ParkNear(b) = goal { - 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) - .bike_to_sidewalk(last_lane) - .is_none() - { - println!( - "Can't fulfill {:?} for a bike trip; no sidewalk near {}. Walking \ - instead.", - goal, last_lane - ); - spec = backup_plan.unwrap(); - } else if map.get_b(*start).sidewalk() == map.get_b(*b).sidewalk() { - // A bike trip going from one lane to the same lane should... just walk. + if let DrivingGoal::ParkNear(b) = goal { + if map.get_b(*start).sidewalk() == map.get_b(*b).sidewalk() { println!( "Bike trip from {} to {:?} will just walk; it's the same sidewalk!", start, goal ); - spec = backup_plan.unwrap(); + spec = TripSpec::JustWalking { + start: SidewalkSpot::building(*start, map), + goal: SidewalkSpot::building(*b, map), + }; } } } @@ -294,9 +252,7 @@ impl TripSpawner { map, ), TripSpec::UsingBike { bike, start, goal } => { - let walk_to = - SidewalkSpot::bike_from_bike_rack(map.get_b(start).sidewalk(), map) - .unwrap(); + let walk_to = SidewalkSpot::bike_rack(start, map); let mut legs = vec![ TripLeg::Walk(walk_to.clone()), TripLeg::Drive(bike, goal.clone()), @@ -400,9 +356,7 @@ impl TripSpec { }), TripSpec::UsingBike { start, .. } => Some(PathRequest { start: map.get_b(*start).sidewalk_pos, - end: SidewalkSpot::bike_from_bike_rack(map.get_b(*start).sidewalk(), map) - .unwrap() - .sidewalk_pos, + end: map.get_b(*start).biking_connection(map).1, constraints: PathConstraints::Pedestrian, }), TripSpec::UsingTransit { start, stop1, .. } => Some(PathRequest { diff --git a/sim/src/mechanics/intersection.rs b/sim/src/mechanics/intersection.rs index 74a2a9779e..692136aa26 100644 --- a/sim/src/mechanics/intersection.rs +++ b/sim/src/mechanics/intersection.rs @@ -301,7 +301,7 @@ impl IntersectionSimState { // If we started an uber-turn, then finish it! But alert if we're running a red light. if let Some(ref signal) = map.maybe_get_traffic_signal(turn.parent) { // Don't pass in the scheduler, aka, don't pause before yielding. - if !self.traffic_signal_policy(&req, map, signal, speed, now, None) { + if !self.traffic_signal_policy(&req, map, signal, speed, now, None) && false { self.events.push(Event::Alert( AlertLocation::Intersection(req.turn.parent), format!("Running a red light inside an uber-turn: {:?}", req), diff --git a/sim/src/router.rs b/sim/src/router.rs index 66c240f8db..d1b4e365e0 100644 --- a/sim/src/router.rs +++ b/sim/src/router.rs @@ -45,7 +45,7 @@ enum Goal { i: IntersectionID, }, BikeThenStop { - end_dist: Distance, + goal: SidewalkSpot, }, FollowBusRoute { end_dist: Distance, @@ -90,26 +90,11 @@ impl Router { } } - pub fn bike_then_stop( - owner: CarID, - path: Path, - end_dist: Distance, - map: &Map, - ) -> Option { - let last_lane = path.get_steps().iter().last().unwrap().as_lane(); - if map - .get_parent(last_lane) - .bike_to_sidewalk(last_lane) - .is_some() - { - Some(Router { - path, - goal: Goal::BikeThenStop { end_dist }, - owner, - }) - } else { - println!("{} is the end of a bike route, with no sidewalk", last_lane); - None + pub fn bike_then_stop(owner: CarID, path: Path, goal: SidewalkSpot) -> Router { + Router { + goal: Goal::BikeThenStop { goal }, + path, + owner, } } @@ -151,7 +136,7 @@ impl Router { stuck_end_dist, .. } => stuck_end_dist.unwrap_or_else(|| spot.unwrap().1), - Goal::BikeThenStop { end_dist } => end_dist, + Goal::BikeThenStop { ref goal } => goal.sidewalk_pos.dist_along(), Goal::FollowBusRoute { end_dist } => end_dist, } } @@ -317,17 +302,9 @@ impl Router { None } } - Goal::BikeThenStop { end_dist } => { - if end_dist == front { - // Checked up-front that this exists - let last_lane = self.head().as_lane(); - let sidewalk = map - .get_parent(last_lane) - .bike_to_sidewalk(last_lane) - .unwrap(); - Some(ActionAtEnd::StopBiking( - SidewalkSpot::bike_rack(sidewalk, map).unwrap(), - )) + Goal::BikeThenStop { ref goal } => { + if goal.sidewalk_pos.dist_along() == front { + Some(ActionAtEnd::StopBiking(goal.clone())) } else { None } diff --git a/sim/src/trips.rs b/sim/src/trips.rs index 2d6df2dab2..fc72f65294 100644 --- a/sim/src/trips.rs +++ b/sim/src/trips.rs @@ -296,9 +296,7 @@ impl TripManager { return; }; - let router = drive_to - .make_router(parked_car.vehicle.id, path, map) - .unwrap(); + let router = drive_to.make_router(parked_car.vehicle.id, path, map); scheduler.push( now, Command::SpawnCar( @@ -350,7 +348,7 @@ impl TripManager { }; if let Some(router) = map .pathfind(req.clone()) - .and_then(|path| drive_to.make_router(bike, path, map)) + .map(|path| drive_to.make_router(bike, path, map)) { scheduler.push( now, @@ -1026,8 +1024,7 @@ impl TripManager { let vehicle = person.get_vehicle(use_vehicle); assert!(parking.lookup_parked_car(vehicle.id).is_none()); let req = maybe_req.unwrap(); - if let Some(router) = - maybe_path.and_then(|path| goal.make_router(vehicle.id, path, map)) + if let Some(router) = maybe_path.map(|path| goal.make_router(vehicle.id, path, map)) { scheduler.push( now, @@ -1042,8 +1039,7 @@ impl TripManager { self.events.push(Event::Alert( AlertLocation::Person(person.id), format!( - "VehicleAppearing trip couldn't find the first path (or no \ - bike->sidewalk connection at the end): {}", + "VehicleAppearing trip couldn't find the first path: {}", req ), )); @@ -1181,8 +1177,7 @@ impl TripManager { assert_eq!(person.state, PersonState::Inside(start)); person.state = PersonState::Trip(trip); - let walk_to = - SidewalkSpot::bike_from_bike_rack(map.get_b(start).sidewalk(), map).unwrap(); + let walk_to = SidewalkSpot::bike_rack(start, map); let req = maybe_req.unwrap(); if let Some(path) = maybe_path { scheduler.push(