try to handle walking around private zones too. gluing the paths

together is buggy, giving up for now and leaving disabled
This commit is contained in:
Dustin Carlino 2020-06-29 16:44:45 -07:00
parent 694b70c438
commit 2e1612c3db
5 changed files with 294 additions and 138 deletions

View File

@ -37,6 +37,7 @@ Measure the effects:
- [Map model](docs/articles/map/article.md)
- [Traffic simulation](docs/articles/trafficsim/article.md)
- [Running A/B Street in a new city](docs/new_city.md)
- [UX design](https://yuwen-li.com/work/abstreet)
- Presentations
- April 2020 Rust meetup:
[recording](https://www.youtube.com/watch?v=chYd5I-5oyc),

View File

@ -273,4 +273,14 @@ impl Lane {
0
})
}
pub fn common_endpt(&self, other: &Lane) -> IntersectionID {
if self.src_i == other.src_i || self.src_i == other.dst_i {
self.src_i
} else if self.dst_i == other.src_i || self.dst_i == other.dst_i {
self.dst_i
} else {
panic!("{} and {} don't share an endpoint", self.id, other.id);
}
}
}

View File

@ -7,6 +7,7 @@ mod walking;
pub use self::driving::cost;
use self::driving::VehiclePathfinder;
use self::walking::SidewalkPathfinder;
pub use self::walking::{one_step_walking_path, walking_cost, walking_path_to_steps, WalkingNode};
use crate::{
osm, BusRouteID, BusStopID, Intersection, Lane, LaneID, LaneType, Map, Position, Traversable,
TurnID, Zone,
@ -320,18 +321,26 @@ impl Path {
}
fn prepend(&mut self, other: Path, map: &Map) {
match (*other.steps.back().unwrap(), self.steps[0]) {
(PathStep::Lane(src), PathStep::Lane(dst)) => {
let turn = TurnID {
parent: map.get_l(src).dst_i,
src,
dst,
};
self.steps.push_front(PathStep::Turn(turn));
self.total_length += map.get_t(turn).geom.length();
let common_i = map
.get_l(other.steps.back().unwrap().as_lane())
.common_endpt(map.get_l(self.steps[0].as_lane()));
match *other.steps.back().unwrap() {
PathStep::Lane(l) => {
if map.get_l(l).src_i == common_i {
self.steps.push_front(PathStep::ContraflowLane(l));
}
}
PathStep::ContraflowLane(l) => {
if map.get_l(l).dst_i == common_i {
self.steps.push_front(PathStep::Lane(l));
}
}
_ => unreachable!(),
}
let turn = glue(*other.steps.back().unwrap(), self.steps[0], map);
self.steps.push_front(PathStep::Turn(turn));
self.total_length += map.get_t(turn).geom.length();
for step in other.steps.into_iter().rev() {
self.steps.push_front(step);
}
@ -340,24 +349,56 @@ impl Path {
}
fn append(&mut self, other: Path, map: &Map) {
match (*self.steps.back().unwrap(), other.steps[0]) {
(PathStep::Lane(src), PathStep::Lane(dst)) => {
let turn = TurnID {
parent: map.get_l(src).dst_i,
src,
dst,
};
self.steps.push_back(PathStep::Turn(turn));
self.total_length += map.get_t(turn).geom.length();
// TODO There's a better way to do this. We might be able to remove a step instead of
// doubling back sometimes.
let common_i = map
.get_l(self.steps.back().unwrap().as_lane())
.common_endpt(map.get_l(other.steps[0].as_lane()));
match *self.steps.back().unwrap() {
PathStep::Lane(l) => {
if map.get_l(l).src_i == common_i {
self.steps.push_back(PathStep::ContraflowLane(l));
}
}
PathStep::ContraflowLane(l) => {
if map.get_l(l).dst_i == common_i {
self.steps.push_back(PathStep::Lane(l));
}
}
_ => unreachable!(),
}
let turn = glue(*self.steps.back().unwrap(), other.steps[0], map);
self.steps.push_back(PathStep::Turn(turn));
self.total_length += map.get_t(turn).geom.length();
self.steps.extend(other.steps);
self.total_length += other.total_length;
self.total_lanes += other.total_lanes;
}
}
fn glue(step1: PathStep, step2: PathStep, map: &Map) -> TurnID {
match step1 {
PathStep::Lane(src) => match step2 {
PathStep::Lane(dst) | PathStep::ContraflowLane(dst) => TurnID {
parent: map.get_l(src).dst_i,
src,
dst,
},
_ => unreachable!(),
},
PathStep::ContraflowLane(src) => match step2 {
PathStep::Lane(dst) | PathStep::ContraflowLane(dst) => TurnID {
parent: map.get_l(src).src_i,
src,
dst,
},
_ => unreachable!(),
},
_ => unreachable!(),
}
}
// Who's asking for a path?
// TODO This is an awful name.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
@ -553,9 +594,6 @@ impl Pathfinder {
let end_r = map.get_parent(req.end.lane());
if start_r.is_private() && end_r.is_private() {
if req.constraints == PathConstraints::Pedestrian {
return None;
}
let zone1 = map.road_to_zone(start_r.id);
let zone2 = map.road_to_zone(end_r.id);
if zone1.id == zone2.id {
@ -634,7 +672,14 @@ impl Pathfinder {
},
map,
)?;
req.start = Position::new(dst, Distance::ZERO);
req.start = Position::new(
dst,
match interior_path.steps.back().unwrap() {
PathStep::Lane(_) => Distance::ZERO,
PathStep::ContraflowLane(_) => map.get_l(dst).length(),
_ => unreachable!(),
},
);
let mut main_path = match req.constraints {
PathConstraints::Pedestrian => self.walking_graph.pathfind(&req, map),
PathConstraints::Car => self.car_graph.pathfind(&req, map).map(|(p, _)| p),
@ -652,15 +697,38 @@ impl Pathfinder {
zone: &Zone,
map: &Map,
) -> Option<Path> {
// TODO Should probably try all combos of incoming/outgoing lanes per border, but I think
// generally one should suffice?
let src = i
// Because sidewalks aren't all immediately linked, insist on a (src, dst) combo that
// are actually connected by a turn.
let src_choices = i
.get_incoming_lanes(map, req.constraints)
.find(|l| !zone.members.contains(&map.get_l(*l).parent))?;
let dst = i
.filter(|l| !zone.members.contains(&map.get_l(*l).parent))
.collect::<Vec<_>>();
let dst_choices = i
.get_outgoing_lanes(map, req.constraints)
.into_iter()
.find(|l| zone.members.contains(&map.get_l(*l).parent))?;
.filter(|l| zone.members.contains(&map.get_l(*l).parent))
.collect::<Vec<_>>();
let (src, dst) = {
let mut result = None;
'OUTER: for l1 in src_choices {
for l2 in &dst_choices {
if l1 != *l2
&& map
.maybe_get_t(TurnID {
parent: i.id,
src: l1,
dst: *l2,
})
.is_some()
{
result = Some((l1, *l2));
break 'OUTER;
}
}
}
result?
};
let interior_path = zone.pathfind(
PathRequest {
start: Position::new(dst, Distance::ZERO),
@ -670,7 +738,15 @@ impl Pathfinder {
map,
)?;
let orig_end_dist = req.end.dist_along();
req.end = Position::new(src, map.get_l(src).length());
req.end = Position::new(
src,
match interior_path.steps[0] {
PathStep::Lane(_) => map.get_l(src).length(),
PathStep::ContraflowLane(_) => Distance::ZERO,
_ => unreachable!(),
},
);
let mut main_path = match req.constraints {
PathConstraints::Pedestrian => self.walking_graph.pathfind(&req, map),
PathConstraints::Car => self.car_graph.pathfind(&req, map).map(|(p, _)| p),

View File

@ -14,34 +14,41 @@ pub struct SidewalkPathfinder {
#[serde(serialize_with = "serialize_32", deserialize_with = "deserialize_32")]
graph: FastGraph,
#[serde(deserialize_with = "deserialize_nodemap")]
nodes: NodeMap<Node>,
nodes: NodeMap<WalkingNode>,
use_transit: bool,
#[serde(skip_serializing, skip_deserializing)]
path_calc: ThreadLocal<RefCell<PathCalculator>>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
enum Node {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Serialize, Deserialize)]
pub enum WalkingNode {
// false is src_i, true is dst_i
SidewalkEndpoint(LaneID, bool),
RideBus(BusStopID),
}
impl WalkingNode {
pub fn closest(pos: Position, map: &Map) -> WalkingNode {
let dst_i = map.get_l(pos.lane()).length() - pos.dist_along() <= pos.dist_along();
WalkingNode::SidewalkEndpoint(pos.lane(), dst_i)
}
}
impl SidewalkPathfinder {
pub fn new(map: &Map, use_transit: bool, bus_graph: &VehiclePathfinder) -> SidewalkPathfinder {
let mut nodes = NodeMap::new();
// We're assuming that to start with, no sidewalks are closed for construction!
for l in map.all_lanes() {
if l.is_sidewalk() {
nodes.get_or_insert(Node::SidewalkEndpoint(l.id, true));
nodes.get_or_insert(Node::SidewalkEndpoint(l.id, false));
nodes.get_or_insert(WalkingNode::SidewalkEndpoint(l.id, true));
nodes.get_or_insert(WalkingNode::SidewalkEndpoint(l.id, false));
}
}
if use_transit {
// Add a node for each bus stop.
for stop in map.all_bus_stops().values() {
nodes.get_or_insert(Node::RideBus(stop.id));
nodes.get_or_insert(WalkingNode::RideBus(stop.id));
}
}
@ -63,30 +70,8 @@ impl SidewalkPathfinder {
}
pub fn pathfind(&self, req: &PathRequest, map: &Map) -> Option<Path> {
// Special-case one-step paths.
// TODO Maybe we don't need these special cases anymore.
if req.start.lane() == req.end.lane() {
// Weird case, but it can happen for walking from a building path to a bus stop that're
// actually at the same spot.
if req.start.dist_along() == req.end.dist_along() {
return Some(Path::new(
map,
vec![PathStep::Lane(req.start.lane())],
req.start.dist_along(),
));
} else if req.start.dist_along() < req.end.dist_along() {
return Some(Path::new(
map,
vec![PathStep::Lane(req.start.lane())],
req.end.dist_along(),
));
} else {
return Some(Path::new(
map,
vec![PathStep::ContraflowLane(req.start.lane())],
req.end.dist_along(),
));
}
return Some(one_step_walking_path(req, map));
}
let mut calc = self
@ -95,62 +80,11 @@ impl SidewalkPathfinder {
.borrow_mut();
let raw_path = calc.calc_path(
&self.graph,
self.nodes.get(closest_node(req.start, map)),
self.nodes.get(closest_node(req.end, map)),
self.nodes.get(WalkingNode::closest(req.start, map)),
self.nodes.get(WalkingNode::closest(req.end, map)),
)?;
let path = self.nodes.translate(&raw_path);
let mut steps: Vec<PathStep> = Vec::new();
for pair in path.windows(2) {
let (l1, l1_endpt) = match pair[0] {
Node::SidewalkEndpoint(l, endpt) => (l, endpt),
Node::RideBus(_) => unreachable!(),
};
let l2 = match pair[1] {
Node::SidewalkEndpoint(l, _) => l,
Node::RideBus(_) => unreachable!(),
};
if l1 == l2 {
if l1_endpt {
steps.push(PathStep::ContraflowLane(l1));
} else {
steps.push(PathStep::Lane(l1));
}
} else {
let i = {
let l = map.get_l(l1);
if l1_endpt {
l.dst_i
} else {
l.src_i
}
};
// Could assert the intersection matches (l2, l2_endpt).
let turn = map.get_turn_between(l1, l2, i).unwrap();
steps.push(PathStep::Turn(turn));
}
}
// Don't start or end a path in a turn; sim layer breaks.
if let PathStep::Turn(t) = steps[0] {
let lane = map.get_l(t.src);
if lane.src_i == t.parent {
steps.insert(0, PathStep::ContraflowLane(lane.id));
} else {
steps.insert(0, PathStep::Lane(lane.id));
}
}
if let PathStep::Turn(t) = steps.last().unwrap() {
let lane = map.get_l(t.dst);
if lane.src_i == t.parent {
steps.push(PathStep::Lane(lane.id));
} else {
steps.push(PathStep::ContraflowLane(lane.id));
}
}
let steps = walking_path_to_steps(path, map);
Some(Path::new(map, steps, req.end.dist_along()))
}
@ -163,14 +97,14 @@ impl SidewalkPathfinder {
) -> Option<(BusStopID, BusStopID, BusRouteID)> {
let raw_path = fast_paths::calc_path(
&self.graph,
self.nodes.get(closest_node(start, map)),
self.nodes.get(closest_node(end, map)),
self.nodes.get(WalkingNode::closest(start, map)),
self.nodes.get(WalkingNode::closest(end, map)),
)?;
let mut nodes = self.nodes.translate(&raw_path);
let mut first_stop = None;
for n in &nodes {
if let Node::RideBus(stop) = n {
if let WalkingNode::RideBus(stop) = n {
first_stop = Some(*stop);
break;
}
@ -180,7 +114,7 @@ impl SidewalkPathfinder {
nodes.reverse();
for n in nodes {
if let Node::RideBus(stop2) = n {
if let WalkingNode::RideBus(stop2) = n {
if let Some(route) = possible_routes.iter().find(|r| r.stops.contains(&stop2)) {
assert_ne!(first_stop, stop2);
return Some((first_stop, stop2, route.id));
@ -191,14 +125,9 @@ impl SidewalkPathfinder {
}
}
fn closest_node(pos: Position, map: &Map) -> Node {
let dst_i = map.get_l(pos.lane()).length() - pos.dist_along() <= pos.dist_along();
Node::SidewalkEndpoint(pos.lane(), dst_i)
}
fn make_input_graph(
map: &Map,
nodes: &NodeMap<Node>,
nodes: &NodeMap<WalkingNode>,
use_transit: bool,
bus_graph: &VehiclePathfinder,
) -> InputGraph {
@ -206,9 +135,9 @@ fn make_input_graph(
for l in map.all_lanes() {
if l.is_sidewalk() && !map.get_r(l.parent).is_private() {
let cost = to_s(l.length());
let n1 = nodes.get(Node::SidewalkEndpoint(l.id, true));
let n2 = nodes.get(Node::SidewalkEndpoint(l.id, false));
let cost = walking_cost(l.length());
let n1 = nodes.get(WalkingNode::SidewalkEndpoint(l.id, true));
let n2 = nodes.get(WalkingNode::SidewalkEndpoint(l.id, false));
input_graph.add_edge(n1, n2, cost);
input_graph.add_edge(n2, n1, cost);
}
@ -216,27 +145,33 @@ fn make_input_graph(
for t in map.all_turns().values() {
if t.between_sidewalks() {
let from = Node::SidewalkEndpoint(t.id.src, map.get_l(t.id.src).dst_i == t.id.parent);
let to = Node::SidewalkEndpoint(t.id.dst, map.get_l(t.id.dst).dst_i == t.id.parent);
input_graph.add_edge(nodes.get(from), nodes.get(to), to_s(t.geom.length()));
let from =
WalkingNode::SidewalkEndpoint(t.id.src, map.get_l(t.id.src).dst_i == t.id.parent);
let to =
WalkingNode::SidewalkEndpoint(t.id.dst, map.get_l(t.id.dst).dst_i == t.id.parent);
input_graph.add_edge(
nodes.get(from),
nodes.get(to),
walking_cost(t.geom.length()),
);
}
}
if use_transit {
// Connect bus stops with both sidewalk endpoints, using the appropriate distance.
for stop in map.all_bus_stops().values() {
let ride_bus = nodes.get(Node::RideBus(stop.id));
let ride_bus = nodes.get(WalkingNode::RideBus(stop.id));
let lane = map.get_l(stop.sidewalk_pos.lane());
for endpt in &[true, false] {
let cost = if *endpt {
to_s(lane.length() - stop.sidewalk_pos.dist_along())
walking_cost(lane.length() - stop.sidewalk_pos.dist_along())
} else {
to_s(stop.sidewalk_pos.dist_along())
walking_cost(stop.sidewalk_pos.dist_along())
};
// Add some extra penalty (equivalent to 1m) to using a bus stop. Otherwise a path
// might try to pass through it uselessly.
let penalty = 100;
let sidewalk = nodes.get(Node::SidewalkEndpoint(lane.id, *endpt));
let sidewalk = nodes.get(WalkingNode::SidewalkEndpoint(lane.id, *endpt));
input_graph.add_edge(sidewalk, ride_bus, cost + penalty);
input_graph.add_edge(ride_bus, sidewalk, cost + penalty);
}
@ -264,8 +199,8 @@ fn make_input_graph(
map,
) {
input_graph.add_edge(
nodes.get(Node::RideBus(*stop1)),
nodes.get(Node::RideBus(*stop2)),
nodes.get(WalkingNode::RideBus(*stop1)),
nodes.get(WalkingNode::RideBus(*stop2)),
driving_cost,
);
} else {
@ -282,8 +217,87 @@ fn make_input_graph(
input_graph
}
fn to_s(dist: Distance) -> usize {
pub fn walking_cost(dist: Distance) -> usize {
let walking_speed = Speed::meters_per_second(1.34);
let time = dist / walking_speed;
(time.inner_seconds().round() as usize).max(1)
}
pub fn walking_path_to_steps(path: Vec<WalkingNode>, map: &Map) -> Vec<PathStep> {
let mut steps: Vec<PathStep> = Vec::new();
for pair in path.windows(2) {
let (l1, l1_endpt) = match pair[0] {
WalkingNode::SidewalkEndpoint(l, endpt) => (l, endpt),
WalkingNode::RideBus(_) => unreachable!(),
};
let l2 = match pair[1] {
WalkingNode::SidewalkEndpoint(l, _) => l,
WalkingNode::RideBus(_) => unreachable!(),
};
if l1 == l2 {
if l1_endpt {
steps.push(PathStep::ContraflowLane(l1));
} else {
steps.push(PathStep::Lane(l1));
}
} else {
let i = {
let l = map.get_l(l1);
if l1_endpt {
l.dst_i
} else {
l.src_i
}
};
// Could assert the intersection matches (l2, l2_endpt).
let turn = map.get_turn_between(l1, l2, i).unwrap();
steps.push(PathStep::Turn(turn));
}
}
// Don't start or end a path in a turn; sim layer breaks.
if let PathStep::Turn(t) = steps[0] {
let lane = map.get_l(t.src);
if lane.src_i == t.parent {
steps.insert(0, PathStep::ContraflowLane(lane.id));
} else {
steps.insert(0, PathStep::Lane(lane.id));
}
}
if let PathStep::Turn(t) = steps.last().unwrap() {
let lane = map.get_l(t.dst);
if lane.src_i == t.parent {
steps.push(PathStep::Lane(lane.id));
} else {
steps.push(PathStep::ContraflowLane(lane.id));
}
}
steps
}
pub fn one_step_walking_path(req: &PathRequest, map: &Map) -> Path {
// Weird case, but it can happen for walking from a building path to a bus stop that're
// actually at the same spot.
if req.start.dist_along() == req.end.dist_along() {
Path::new(
map,
vec![PathStep::Lane(req.start.lane())],
req.start.dist_along(),
)
} else if req.start.dist_along() < req.end.dist_along() {
Path::new(
map,
vec![PathStep::Lane(req.start.lane())],
req.end.dist_along(),
)
} else {
Path::new(
map,
vec![PathStep::ContraflowLane(req.start.lane())],
req.end.dist_along(),
)
}
}

View File

@ -1,5 +1,9 @@
use crate::pathfind::cost;
use crate::{IntersectionID, LaneID, Map, Path, PathRequest, PathStep, RoadID, TurnID};
use crate::pathfind::{
cost, one_step_walking_path, walking_cost, walking_path_to_steps, WalkingNode,
};
use crate::{
IntersectionID, LaneID, Map, Path, PathConstraints, PathRequest, PathStep, RoadID, TurnID,
};
use petgraph::graphmap::DiGraphMap;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
@ -25,7 +29,11 @@ pub struct Zone {
impl Zone {
// Run slower Dijkstra's within the interior of a private zone. Don't go outside the borders.
pub(crate) fn pathfind(&self, req: PathRequest, map: &Map) -> Option<Path> {
// Edge type is the Turn, but we don't need it
// TODO Not happy this works so differently
if req.constraints == PathConstraints::Pedestrian {
return self.pathfind_walking(req, map);
}
let mut graph: DiGraphMap<LaneID, TurnID> = DiGraphMap::new();
for r in &self.members {
for l in map.get_r(*r).all_lanes() {
@ -60,4 +68,51 @@ impl Zone {
assert_eq!(steps[0], PathStep::Lane(req.start.lane()));
Some(Path::new(map, steps, req.end.dist_along()))
}
fn pathfind_walking(&self, req: PathRequest, map: &Map) -> Option<Path> {
if req.start.lane() == req.end.lane() {
return Some(one_step_walking_path(&req, map));
}
let mut graph: DiGraphMap<WalkingNode, usize> = DiGraphMap::new();
for r in &self.members {
for l in map.get_r(*r).all_lanes() {
let l = map.get_l(l);
if l.is_sidewalk() {
let cost = walking_cost(l.length());
let n1 = WalkingNode::SidewalkEndpoint(l.id, true);
let n2 = WalkingNode::SidewalkEndpoint(l.id, false);
graph.add_edge(n1, n2, cost);
graph.add_edge(n2, n1, cost);
for turn in map.get_turns_for(l.id, PathConstraints::Pedestrian) {
if self.members.contains(&map.get_l(turn.id.dst).parent) {
graph.add_edge(
WalkingNode::SidewalkEndpoint(l.id, l.dst_i == turn.id.parent),
WalkingNode::SidewalkEndpoint(
turn.id.dst,
map.get_l(turn.id.dst).dst_i == turn.id.parent,
),
walking_cost(turn.geom.length()),
);
}
}
}
}
}
let closest_start = WalkingNode::closest(req.start, map);
let closest_end = WalkingNode::closest(req.end, map);
let (_, path) = petgraph::algo::astar(
&graph,
closest_start,
|end| end == closest_end,
|(_, _, cost)| *cost,
|_| 0,
)?;
let steps = walking_path_to_steps(path, map);
assert_eq!(steps[0].as_lane(), req.start.lane());
assert_eq!(steps.last().unwrap().as_lane(), req.end.lane());
Some(Path::new(map, steps, req.end.dist_along()))
}
}