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(