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)
This commit is contained in:
Dustin Carlino 2020-07-31 14:28:40 -07:00
parent d22aa87139
commit 5c506f726c
8 changed files with 127 additions and 222 deletions

View File

@ -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<RoadID> = VecDeque::new();
let mut visited: HashSet<RoadID> = 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 { pub fn get_boundary_polygon(&self) -> &Polygon {
&self.boundary_polygon &self.boundary_polygon
} }

View File

@ -1,8 +1,8 @@
use crate::{LaneID, LaneType, Map, Position}; use crate::{LaneID, LaneType, Map, PathConstraints, Position};
use abstutil::{deserialize_usize, serialize_usize}; use abstutil::{deserialize_usize, serialize_usize};
use geom::{Distance, PolyLine, Polygon, Pt2D}; use geom::{Distance, PolyLine, Polygon, Pt2D};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeSet; use std::collections::{BTreeSet, HashSet, VecDeque};
use std::fmt; use std::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[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 // 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)> { pub fn driving_connection(&self, map: &Map) -> Option<(Position, PolyLine)> {
// Is there even a driving lane on the same side as our sidewalk? // Is there even a driving lane on the same side as our sidewalk?
// TODO Handle offside // TODO Handle offside
@ -101,6 +102,40 @@ impl Building {
Some((pos, self.driveway_geom.clone().must_push(pos.pt(map)))) 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<LaneID> = VecDeque::new();
let mut visited: HashSet<LaneID> = 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 { pub fn num_parking_spots(&self) -> usize {
match self.parking { match self.parking {
OffstreetParking::PublicGarage(_, n) => n, 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,
))
}

View File

@ -1,5 +1,5 @@
use crate::raw::{OriginalRoad, RestrictionType}; 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 abstutil::{deserialize_usize, serialize_usize, Tags};
use enumset::EnumSet; use enumset::EnumSet;
use geom::{Distance, PolyLine, Polygon, Speed}; use geom::{Distance, PolyLine, Polygon, Speed};
@ -170,28 +170,6 @@ impl Road {
.map(|(id, _)| *id) .map(|(id, _)| *id)
} }
pub fn sidewalk_to_bike(&self, sidewalk: LaneID) -> Option<LaneID> {
// 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<LaneID> {
// 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 { pub(crate) fn speed_limit_from_osm(&self) -> Speed {
if let Some(limit) = self.osm_tags.get(osm::MAXSPEED) { if let Some(limit) = self.osm_tags.get(osm::MAXSPEED) {
if let Ok(kmph) = limit.parse::<f64>() { if let Ok(kmph) = limit.parse::<f64>() {
@ -271,6 +249,42 @@ impl Road {
} }
} }
// TODO Migrate and rip out all the old stuff
pub(crate) fn find_closest_lane_v2<F: Fn(&Lane) -> bool>(
&self,
from: LaneID,
include_offside: bool,
filter: F,
map: &Map,
) -> Option<LaneID> {
// (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<LaneID> { pub fn all_lanes(&self) -> Vec<LaneID> {
self.children_forwards self.children_forwards
.iter() .iter()

View File

@ -332,10 +332,7 @@ impl DrivingGoal {
match self { match self {
DrivingGoal::ParkNear(b) => match constraints { DrivingGoal::ParkNear(b) => match constraints {
PathConstraints::Car => Position::start(map.find_driving_lane_near_building(*b)), PathConstraints::Car => Position::start(map.find_driving_lane_near_building(*b)),
PathConstraints::Bike => { PathConstraints::Bike => map.get_b(*b).biking_connection(map).0,
let l = map.find_biking_lane_near_building(*b);
Position::new(l, map.get_l(l).length() / 2.0)
}
PathConstraints::Bus | PathConstraints::Train | PathConstraints::Pedestrian => { PathConstraints::Bus | PathConstraints::Train | PathConstraints::Pedestrian => {
unreachable!() 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) -> Router {
pub(crate) fn make_router(&self, owner: CarID, path: Path, map: &Map) -> Option<Router> {
match self { match self {
DrivingGoal::ParkNear(b) => { DrivingGoal::ParkNear(b) => {
if owner.1 == VehicleType::Bike { if owner.1 == VehicleType::Bike {
// TODO Stop closer to the building? Router::bike_then_stop(owner, path, SidewalkSpot::bike_rack(*b, map))
let end = path.last_step().as_lane();
Router::bike_then_stop(owner, path, map.get_l(end).length() / 2.0, map)
} else { } else {
Some(Router::park_near(owner, path, *b)) Router::park_near(owner, path, *b)
} }
} }
DrivingGoal::Border(i, last_lane, _) => Some(Router::end_at_border( DrivingGoal::Border(i, last_lane, _) => {
owner, Router::end_at_border(owner, path, map.get_l(*last_lane).length(), *i)
path, }
map.get_l(*last_lane).length(),
*i,
)),
} }
} }
@ -389,7 +380,7 @@ pub enum SidewalkPOI {
Building(BuildingID), Building(BuildingID),
BusStop(BusStopID), BusStop(BusStopID),
Border(IntersectionID, Option<OffMapLocation>), Border(IntersectionID, Option<OffMapLocation>),
// The equivalent position on the nearest driving/bike lane // The bikeable position
BikeRack(Position), BikeRack(Position),
SuddenlyAppear, SuddenlyAppear,
} }
@ -422,38 +413,13 @@ impl SidewalkSpot {
} }
} }
pub fn bike_rack(sidewalk: LaneID, map: &Map) -> Option<SidewalkSpot> { // TODO For the case when we have to start/stop biking somewhere else, this won't match up with
assert!(map.get_l(sidewalk).is_walkable()); // a building though!
let driving_lane = map.get_parent(sidewalk).sidewalk_to_bike(sidewalk)?; pub fn bike_rack(b: BuildingID, map: &Map) -> SidewalkSpot {
// TODO Arbitrary, but safe let (bike_pos, sidewalk_pos) = map.get_b(b).biking_connection(map);
let sidewalk_pos = Position::new(sidewalk, map.get_l(sidewalk).length() / 2.0); SidewalkSpot {
let driving_pos = sidewalk_pos.equiv_pos(driving_lane, Distance::ZERO, map); connection: SidewalkPOI::BikeRack(bike_pos),
Some(SidewalkSpot {
connection: SidewalkPOI::BikeRack(driving_pos),
sidewalk_pos, sidewalk_pos,
})
}
pub fn bike_from_bike_rack(sidewalk: LaneID, map: &Map) -> Option<SidewalkSpot> {
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)
} }
} }

View File

@ -114,58 +114,16 @@ impl TripSpawner {
} }
} }
TripSpec::UsingBike { start, goal, .. } => { TripSpec::UsingBike { start, goal, .. } => {
// TODO Might not be possible to walk to the same border if there's no sidewalk if let DrivingGoal::ParkNear(b) = goal {
let backup_plan = match goal { if map.get_b(*start).sidewalk() == map.get_b(*b).sidewalk() {
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.
println!( println!(
"Bike trip from {} to {:?} will just walk; it's the same sidewalk!", "Bike trip from {} to {:?} will just walk; it's the same sidewalk!",
start, goal 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, map,
), ),
TripSpec::UsingBike { bike, start, goal } => { TripSpec::UsingBike { bike, start, goal } => {
let walk_to = let walk_to = SidewalkSpot::bike_rack(start, map);
SidewalkSpot::bike_from_bike_rack(map.get_b(start).sidewalk(), map)
.unwrap();
let mut legs = vec![ let mut legs = vec![
TripLeg::Walk(walk_to.clone()), TripLeg::Walk(walk_to.clone()),
TripLeg::Drive(bike, goal.clone()), TripLeg::Drive(bike, goal.clone()),
@ -400,9 +356,7 @@ impl TripSpec {
}), }),
TripSpec::UsingBike { start, .. } => Some(PathRequest { TripSpec::UsingBike { start, .. } => Some(PathRequest {
start: map.get_b(*start).sidewalk_pos, start: map.get_b(*start).sidewalk_pos,
end: SidewalkSpot::bike_from_bike_rack(map.get_b(*start).sidewalk(), map) end: map.get_b(*start).biking_connection(map).1,
.unwrap()
.sidewalk_pos,
constraints: PathConstraints::Pedestrian, constraints: PathConstraints::Pedestrian,
}), }),
TripSpec::UsingTransit { start, stop1, .. } => Some(PathRequest { TripSpec::UsingTransit { start, stop1, .. } => Some(PathRequest {

View File

@ -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 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) { if let Some(ref signal) = map.maybe_get_traffic_signal(turn.parent) {
// Don't pass in the scheduler, aka, don't pause before yielding. // 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( self.events.push(Event::Alert(
AlertLocation::Intersection(req.turn.parent), AlertLocation::Intersection(req.turn.parent),
format!("Running a red light inside an uber-turn: {:?}", req), format!("Running a red light inside an uber-turn: {:?}", req),

View File

@ -45,7 +45,7 @@ enum Goal {
i: IntersectionID, i: IntersectionID,
}, },
BikeThenStop { BikeThenStop {
end_dist: Distance, goal: SidewalkSpot,
}, },
FollowBusRoute { FollowBusRoute {
end_dist: Distance, end_dist: Distance,
@ -90,26 +90,11 @@ impl Router {
} }
} }
pub fn bike_then_stop( pub fn bike_then_stop(owner: CarID, path: Path, goal: SidewalkSpot) -> Router {
owner: CarID, Router {
path: Path, goal: Goal::BikeThenStop { goal },
end_dist: Distance,
map: &Map,
) -> Option<Router> {
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, path,
goal: Goal::BikeThenStop { end_dist },
owner, owner,
})
} else {
println!("{} is the end of a bike route, with no sidewalk", last_lane);
None
} }
} }
@ -151,7 +136,7 @@ impl Router {
stuck_end_dist, stuck_end_dist,
.. ..
} => stuck_end_dist.unwrap_or_else(|| spot.unwrap().1), } => 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, Goal::FollowBusRoute { end_dist } => end_dist,
} }
} }
@ -317,17 +302,9 @@ impl Router {
None None
} }
} }
Goal::BikeThenStop { end_dist } => { Goal::BikeThenStop { ref goal } => {
if end_dist == front { if goal.sidewalk_pos.dist_along() == front {
// Checked up-front that this exists Some(ActionAtEnd::StopBiking(goal.clone()))
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(),
))
} else { } else {
None None
} }

View File

@ -296,9 +296,7 @@ impl TripManager {
return; return;
}; };
let router = drive_to let router = drive_to.make_router(parked_car.vehicle.id, path, map);
.make_router(parked_car.vehicle.id, path, map)
.unwrap();
scheduler.push( scheduler.push(
now, now,
Command::SpawnCar( Command::SpawnCar(
@ -350,7 +348,7 @@ impl TripManager {
}; };
if let Some(router) = map if let Some(router) = map
.pathfind(req.clone()) .pathfind(req.clone())
.and_then(|path| drive_to.make_router(bike, path, map)) .map(|path| drive_to.make_router(bike, path, map))
{ {
scheduler.push( scheduler.push(
now, now,
@ -1026,8 +1024,7 @@ impl TripManager {
let vehicle = person.get_vehicle(use_vehicle); let vehicle = person.get_vehicle(use_vehicle);
assert!(parking.lookup_parked_car(vehicle.id).is_none()); assert!(parking.lookup_parked_car(vehicle.id).is_none());
let req = maybe_req.unwrap(); let req = maybe_req.unwrap();
if let Some(router) = if let Some(router) = maybe_path.map(|path| goal.make_router(vehicle.id, path, map))
maybe_path.and_then(|path| goal.make_router(vehicle.id, path, map))
{ {
scheduler.push( scheduler.push(
now, now,
@ -1042,8 +1039,7 @@ impl TripManager {
self.events.push(Event::Alert( self.events.push(Event::Alert(
AlertLocation::Person(person.id), AlertLocation::Person(person.id),
format!( format!(
"VehicleAppearing trip couldn't find the first path (or no \ "VehicleAppearing trip couldn't find the first path: {}",
bike->sidewalk connection at the end): {}",
req req
), ),
)); ));
@ -1181,8 +1177,7 @@ impl TripManager {
assert_eq!(person.state, PersonState::Inside(start)); assert_eq!(person.state, PersonState::Inside(start));
person.state = PersonState::Trip(trip); person.state = PersonState::Trip(trip);
let walk_to = let walk_to = SidewalkSpot::bike_rack(start, map);
SidewalkSpot::bike_from_bike_rack(map.get_b(start).sidewalk(), map).unwrap();
let req = maybe_req.unwrap(); let req = maybe_req.unwrap();
if let Some(path) = maybe_path { if let Some(path) = maybe_path {
scheduler.push( scheduler.push(