mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-18 03:41:52 +03:00
The v1 path that all callers need is available by transforming PathV2 to v1. The plan is to now gradually change callers to natively use PathV2 instead.
This commit is contained in:
parent
618fcdad62
commit
3060f94989
@ -106,7 +106,8 @@ pub fn debug_vehicle_costs(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, cost) = crate::pathfind::dijkstra::pathfind(req.clone(), map.routing_params(), map)?;
|
let cost =
|
||||||
|
crate::pathfind::dijkstra::pathfind(req.clone(), map.routing_params(), map)?.get_cost();
|
||||||
|
|
||||||
let graph = build_graph_for_vehicles(map, req.constraints);
|
let graph = build_graph_for_vehicles(map, req.constraints);
|
||||||
let road_costs = petgraph::algo::dijkstra(
|
let road_costs = petgraph::algo::dijkstra(
|
||||||
|
@ -56,7 +56,9 @@ pub use crate::objects::turn::{
|
|||||||
pub use crate::objects::zone::{AccessRestrictions, Zone};
|
pub use crate::objects::zone::{AccessRestrictions, Zone};
|
||||||
pub use crate::pathfind::uber_turns::{IntersectionCluster, UberTurn};
|
pub use crate::pathfind::uber_turns::{IntersectionCluster, UberTurn};
|
||||||
use crate::pathfind::Pathfinder;
|
use crate::pathfind::Pathfinder;
|
||||||
pub use crate::pathfind::{Path, PathConstraints, PathRequest, PathStep, RoutingParams};
|
pub use crate::pathfind::{
|
||||||
|
Path, PathConstraints, PathRequest, PathStep, PathStepV2, PathV2, RoutingParams,
|
||||||
|
};
|
||||||
pub use crate::traversable::{Position, Traversable, MAX_BIKE_SPEED, MAX_WALKING_SPEED};
|
pub use crate::traversable::{Position, Traversable, MAX_BIKE_SPEED, MAX_WALKING_SPEED};
|
||||||
|
|
||||||
mod city;
|
mod city;
|
||||||
|
@ -579,23 +579,28 @@ impl Map {
|
|||||||
|
|
||||||
pub fn pathfind(&self, req: PathRequest) -> Result<Path> {
|
pub fn pathfind(&self, req: PathRequest) -> Result<Path> {
|
||||||
assert!(!self.pathfinder_dirty);
|
assert!(!self.pathfinder_dirty);
|
||||||
self.pathfinder
|
let path = self
|
||||||
|
.pathfinder
|
||||||
.pathfind(req.clone(), self)
|
.pathfind(req.clone(), self)
|
||||||
.ok_or_else(|| anyhow!("can't fulfill {}", req))
|
.ok_or_else(|| anyhow!("can't fulfill {}", req))?;
|
||||||
|
path.to_v1(self)
|
||||||
}
|
}
|
||||||
pub fn pathfind_avoiding_roads(
|
pub fn pathfind_avoiding_roads(
|
||||||
&self,
|
&self,
|
||||||
req: PathRequest,
|
req: PathRequest,
|
||||||
avoid: BTreeSet<RoadID>,
|
avoid: BTreeSet<RoadID>,
|
||||||
) -> Option<Path> {
|
) -> Result<Path> {
|
||||||
assert!(!self.pathfinder_dirty);
|
assert!(!self.pathfinder_dirty);
|
||||||
self.pathfinder.pathfind_avoiding_roads(req, avoid, self)
|
let path = self.pathfinder.pathfind_avoiding_roads(req, avoid, self)?;
|
||||||
|
path.to_v1(self)
|
||||||
}
|
}
|
||||||
pub fn pathfind_with_params(&self, req: PathRequest, params: &RoutingParams) -> Result<Path> {
|
pub fn pathfind_with_params(&self, req: PathRequest, params: &RoutingParams) -> Result<Path> {
|
||||||
assert!(!self.pathfinder_dirty);
|
assert!(!self.pathfinder_dirty);
|
||||||
self.pathfinder
|
let path = self
|
||||||
|
.pathfinder
|
||||||
.pathfind_with_params(req.clone(), params, self)
|
.pathfind_with_params(req.clone(), params, self)
|
||||||
.ok_or_else(|| anyhow!("can't fulfill {}", req))
|
.ok_or_else(|| anyhow!("can't fulfill {}", req))?;
|
||||||
|
path.to_v1(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_use_transit(
|
pub fn should_use_transit(
|
||||||
|
@ -9,7 +9,7 @@ use geom::Duration;
|
|||||||
use crate::pathfind::dijkstra;
|
use crate::pathfind::dijkstra;
|
||||||
use crate::pathfind::vehicles::VehiclePathfinder;
|
use crate::pathfind::vehicles::VehiclePathfinder;
|
||||||
use crate::pathfind::walking::SidewalkPathfinder;
|
use crate::pathfind::walking::SidewalkPathfinder;
|
||||||
use crate::{BusRouteID, BusStopID, Map, Path, PathConstraints, PathRequest, Position};
|
use crate::{BusRouteID, BusStopID, Map, PathConstraints, PathRequest, PathV2, Position};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ContractionHierarchyPathfinder {
|
pub struct ContractionHierarchyPathfinder {
|
||||||
@ -53,8 +53,8 @@ impl ContractionHierarchyPathfinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<Path> {
|
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<PathV2> {
|
||||||
(match req.constraints {
|
match req.constraints {
|
||||||
PathConstraints::Pedestrian => self.walking_graph.pathfind(req, map),
|
PathConstraints::Pedestrian => self.walking_graph.pathfind(req, map),
|
||||||
PathConstraints::Car => self.car_graph.pathfind(req, map),
|
PathConstraints::Car => self.car_graph.pathfind(req, map),
|
||||||
PathConstraints::Bike => self.bike_graph.pathfind(req, map),
|
PathConstraints::Bike => self.bike_graph.pathfind(req, map),
|
||||||
@ -62,8 +62,7 @@ impl ContractionHierarchyPathfinder {
|
|||||||
// Light rail networks are absolutely tiny; using a contraction hierarchy for them is
|
// Light rail networks are absolutely tiny; using a contraction hierarchy for them is
|
||||||
// overkill. And in fact, it costs a bit of memory and file size, so don't do it!
|
// overkill. And in fact, it costs a bit of memory and file size, so don't do it!
|
||||||
PathConstraints::Train => dijkstra::pathfind(req, map.routing_params(), map),
|
PathConstraints::Train => dijkstra::pathfind(req, map.routing_params(), map),
|
||||||
})
|
}
|
||||||
.map(|(path, _)| path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_use_transit(
|
pub fn should_use_transit(
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use petgraph::graphmap::DiGraphMap;
|
use petgraph::graphmap::DiGraphMap;
|
||||||
|
|
||||||
use geom::Duration;
|
use geom::Duration;
|
||||||
|
|
||||||
use crate::pathfind::v2::path_v2_to_v1;
|
|
||||||
use crate::pathfind::walking::{one_step_walking_path, walking_path_to_steps, WalkingNode};
|
use crate::pathfind::walking::{one_step_walking_path, walking_path_to_steps, WalkingNode};
|
||||||
use crate::pathfind::{vehicle_cost, zone_cost};
|
use crate::pathfind::{vehicle_cost, zone_cost};
|
||||||
use crate::{
|
use crate::{
|
||||||
DirectedRoadID, Map, MovementID, Path, PathConstraints, PathRequest, RoadID, RoutingParams,
|
DirectedRoadID, Map, MovementID, PathConstraints, PathRequest, PathV2, RoadID, RoutingParams,
|
||||||
Traversable,
|
Traversable,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO These should maybe keep the DiGraphMaps as state. It's cheap to recalculate it for edits.
|
// TODO These should maybe keep the DiGraphMaps as state. It's cheap to recalculate it for edits.
|
||||||
|
|
||||||
pub fn pathfind(req: PathRequest, params: &RoutingParams, map: &Map) -> Option<(Path, Duration)> {
|
pub fn pathfind(req: PathRequest, params: &RoutingParams, map: &Map) -> Option<PathV2> {
|
||||||
if req.constraints == PathConstraints::Pedestrian {
|
if req.constraints == PathConstraints::Pedestrian {
|
||||||
pathfind_walking(req, map)
|
pathfind_walking(req, map)
|
||||||
} else {
|
} else {
|
||||||
@ -42,7 +42,7 @@ pub fn pathfind_avoiding_roads(
|
|||||||
req: PathRequest,
|
req: PathRequest,
|
||||||
avoid: BTreeSet<RoadID>,
|
avoid: BTreeSet<RoadID>,
|
||||||
map: &Map,
|
map: &Map,
|
||||||
) -> Option<(Path, Duration)> {
|
) -> Result<PathV2> {
|
||||||
assert_eq!(req.constraints, PathConstraints::Car);
|
assert_eq!(req.constraints, PathConstraints::Car);
|
||||||
let mut graph = DiGraphMap::new();
|
let mut graph = DiGraphMap::new();
|
||||||
for dr in map.all_directed_roads_for(req.constraints) {
|
for dr in map.all_directed_roads_for(req.constraints) {
|
||||||
@ -54,7 +54,8 @@ pub fn pathfind_avoiding_roads(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calc_path(graph, req, map.routing_params(), map)
|
calc_path(graph, req.clone(), map.routing_params(), map)
|
||||||
|
.ok_or_else(|| anyhow!("No path for {} avoiding {} roads", req, avoid.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_path(
|
fn calc_path(
|
||||||
@ -62,9 +63,9 @@ fn calc_path(
|
|||||||
req: PathRequest,
|
req: PathRequest,
|
||||||
params: &RoutingParams,
|
params: &RoutingParams,
|
||||||
map: &Map,
|
map: &Map,
|
||||||
) -> Option<(Path, Duration)> {
|
) -> Option<PathV2> {
|
||||||
let end = map.get_l(req.end.lane()).get_directed_parent();
|
let end = map.get_l(req.end.lane()).get_directed_parent();
|
||||||
let (cost, path) = petgraph::algo::astar(
|
let (cost, steps) = petgraph::algo::astar(
|
||||||
&graph,
|
&graph,
|
||||||
map.get_l(req.start.lane()).get_directed_parent(),
|
map.get_l(req.start.lane()).get_directed_parent(),
|
||||||
|dr| dr == end,
|
|dr| dr == end,
|
||||||
@ -74,10 +75,8 @@ fn calc_path(
|
|||||||
},
|
},
|
||||||
|_| Duration::ZERO,
|
|_| Duration::ZERO,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// TODO No uber-turns yet
|
// TODO No uber-turns yet
|
||||||
let path = path_v2_to_v1(req, path, Vec::new(), map).ok()?;
|
Some(PathV2::from_roads(steps, req, cost, Vec::new(), map))
|
||||||
Some((path, cost))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_graph_for_pedestrians(map: &Map) -> DiGraphMap<WalkingNode, Duration> {
|
pub fn build_graph_for_pedestrians(map: &Map) -> DiGraphMap<WalkingNode, Duration> {
|
||||||
@ -120,7 +119,7 @@ pub fn build_graph_for_pedestrians(map: &Map) -> DiGraphMap<WalkingNode, Duratio
|
|||||||
graph
|
graph
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pathfind_walking(req: PathRequest, map: &Map) -> Option<(Path, Duration)> {
|
fn pathfind_walking(req: PathRequest, map: &Map) -> Option<PathV2> {
|
||||||
if req.start.lane() == req.end.lane() {
|
if req.start.lane() == req.end.lane() {
|
||||||
return Some(one_step_walking_path(req, map));
|
return Some(one_step_walking_path(req, map));
|
||||||
}
|
}
|
||||||
@ -137,5 +136,5 @@ fn pathfind_walking(req: PathRequest, map: &Map) -> Option<(Path, Duration)> {
|
|||||||
|_| Duration::ZERO,
|
|_| Duration::ZERO,
|
||||||
)?;
|
)?;
|
||||||
let steps = walking_path_to_steps(nodes, map);
|
let steps = walking_path_to_steps(nodes, map);
|
||||||
Some((Path::new(map, steps, req, Vec::new()), cost))
|
Some(PathV2::new(steps, req, cost, Vec::new()))
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ pub use self::ch::ContractionHierarchyPathfinder;
|
|||||||
pub use self::dijkstra::{build_graph_for_pedestrians, build_graph_for_vehicles};
|
pub use self::dijkstra::{build_graph_for_pedestrians, build_graph_for_vehicles};
|
||||||
pub use self::pathfinder::Pathfinder;
|
pub use self::pathfinder::Pathfinder;
|
||||||
pub use self::v1::{Path, PathRequest, PathStep};
|
pub use self::v1::{Path, PathRequest, PathStep};
|
||||||
|
pub use self::v2::{PathStepV2, PathV2};
|
||||||
pub use self::vehicles::vehicle_cost;
|
pub use self::vehicles::vehicle_cost;
|
||||||
pub use self::walking::WalkingNode;
|
pub use self::walking::WalkingNode;
|
||||||
use crate::{osm, Lane, LaneID, LaneType, Map, MovementID};
|
use crate::{osm, Lane, LaneID, LaneType, Map, MovementID};
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use abstutil::Timer;
|
use abstutil::Timer;
|
||||||
|
|
||||||
use crate::pathfind::ch::ContractionHierarchyPathfinder;
|
use crate::pathfind::ch::ContractionHierarchyPathfinder;
|
||||||
use crate::pathfind::dijkstra;
|
use crate::pathfind::dijkstra;
|
||||||
use crate::{BusRouteID, BusStopID, Map, Path, PathRequest, Position, RoadID, RoutingParams};
|
use crate::{BusRouteID, BusStopID, Map, PathRequest, PathV2, Position, RoadID, RoutingParams};
|
||||||
|
|
||||||
/// Most of the time, prefer using the faster contraction hierarchies. But sometimes, callers can
|
/// Most of the time, prefer using the faster contraction hierarchies. But sometimes, callers can
|
||||||
/// explicitly opt into a slower (but preparation-free) pathfinder that just uses Dijkstra's
|
/// explicitly opt into a slower (but preparation-free) pathfinder that just uses Dijkstra's
|
||||||
@ -19,7 +20,7 @@ pub enum Pathfinder {
|
|||||||
|
|
||||||
impl Pathfinder {
|
impl Pathfinder {
|
||||||
/// Finds a path from a start to an end for a certain type of agent.
|
/// Finds a path from a start to an end for a certain type of agent.
|
||||||
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<Path> {
|
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<PathV2> {
|
||||||
self.pathfind_with_params(req, map.routing_params(), map)
|
self.pathfind_with_params(req, map.routing_params(), map)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,17 +31,17 @@ impl Pathfinder {
|
|||||||
req: PathRequest,
|
req: PathRequest,
|
||||||
params: &RoutingParams,
|
params: &RoutingParams,
|
||||||
map: &Map,
|
map: &Map,
|
||||||
) -> Option<Path> {
|
) -> Option<PathV2> {
|
||||||
if params != map.routing_params() {
|
if params != map.routing_params() {
|
||||||
// If the params differ from the ones baked into the map, the CHs won't match. This
|
// If the params differ from the ones baked into the map, the CHs won't match. This
|
||||||
// should only be happening from the debug UI; be very obnoxious if we start calling it
|
// should only be happening from the debug UI; be very obnoxious if we start calling it
|
||||||
// from the simulation or something else.
|
// from the simulation or something else.
|
||||||
warn!("Pathfinding slowly for {} with custom params", req);
|
warn!("Pathfinding slowly for {} with custom params", req);
|
||||||
return dijkstra::pathfind(req, params, map).map(|(path, _)| path);
|
return dijkstra::pathfind(req, params, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Pathfinder::Dijkstra => dijkstra::pathfind(req, params, map).map(|(path, _)| path),
|
Pathfinder::Dijkstra => dijkstra::pathfind(req, params, map),
|
||||||
Pathfinder::CH(ref p) => p.pathfind(req, map),
|
Pathfinder::CH(ref p) => p.pathfind(req, map),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,8 +53,8 @@ impl Pathfinder {
|
|||||||
req: PathRequest,
|
req: PathRequest,
|
||||||
avoid: BTreeSet<RoadID>,
|
avoid: BTreeSet<RoadID>,
|
||||||
map: &Map,
|
map: &Map,
|
||||||
) -> Option<Path> {
|
) -> Result<PathV2> {
|
||||||
dijkstra::pathfind_avoiding_roads(req, avoid, map).map(|(path, _)| path)
|
dijkstra::pathfind_avoiding_roads(req, avoid, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Consider returning the walking-only path in the failure case, to avoid wasting work
|
// TODO Consider returning the walking-only path in the failure case, to avoid wasting work
|
||||||
|
@ -3,82 +3,181 @@
|
|||||||
//! things here will probably move into pathfind/mod.rs.
|
//! things here will probably move into pathfind/mod.rs.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use geom::Duration;
|
||||||
|
|
||||||
use crate::pathfind::uber_turns::UberTurnV2;
|
use crate::pathfind::uber_turns::UberTurnV2;
|
||||||
use crate::{DirectedRoadID, Map, Path, PathConstraints, PathRequest, PathStep, TurnID, UberTurn};
|
use crate::{
|
||||||
|
DirectedRoadID, Map, MovementID, Path, PathConstraints, PathRequest, PathStep, TurnID, UberTurn,
|
||||||
|
};
|
||||||
|
|
||||||
/// Transform a sequence of roads representing a path into the current lane-based path, by picking
|
/// One step along a path.
|
||||||
/// particular lanes and turns to use.
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub fn path_v2_to_v1(
|
pub enum PathStepV2 {
|
||||||
|
/// Original direction
|
||||||
|
Along(DirectedRoadID),
|
||||||
|
/// Opposite direction, sidewalks only
|
||||||
|
Contraflow(DirectedRoadID),
|
||||||
|
Movement(MovementID),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A path between two endpoints for a particular mode. This representation is immutable and doesn't
|
||||||
|
/// prescribe specific lanes and turns to follow.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PathV2 {
|
||||||
|
steps: Vec<PathStepV2>,
|
||||||
|
// TODO There will be a PathRequestV2, but I'm not sure how it'll change yet.
|
||||||
req: PathRequest,
|
req: PathRequest,
|
||||||
road_steps: Vec<DirectedRoadID>,
|
cost: Duration,
|
||||||
uber_turns_v2: Vec<UberTurnV2>,
|
// TODO Temporarily we'll keep plumbing these along for path_v2_to_v1 to work, but we'll
|
||||||
map: &Map,
|
// probably just discover uber-turns lazily at the simulation layer instead.
|
||||||
) -> Result<Path> {
|
uber_turns: Vec<UberTurnV2>,
|
||||||
// This is a somewhat brute-force method: run Dijkstra's algorithm on a graph of lanes and
|
}
|
||||||
// turns, but only build the graph along the path of roads we've already found. This handles
|
|
||||||
// arbitrary lookahead needed, and forces use of the original start/end lanes requested.
|
impl PathV2 {
|
||||||
//
|
pub(crate) fn new(
|
||||||
// Eventually we'll directly return road-based paths. Most callers will actually just use that
|
steps: Vec<PathStepV2>,
|
||||||
// directly, and mainly the simulation will need to expand to specific lanes, but it'll do so
|
req: PathRequest,
|
||||||
// dynamically/lazily to account for current traffic conditions.
|
cost: Duration,
|
||||||
let mut graph = petgraph::graphmap::DiGraphMap::new();
|
uber_turns: Vec<UberTurnV2>,
|
||||||
for pair in road_steps.windows(2) {
|
) -> PathV2 {
|
||||||
for src in pair[0].lanes(req.constraints, map) {
|
// TODO Port validate_continuity and validate_restrictions?
|
||||||
for dst in pair[1].lanes(req.constraints, map) {
|
PathV2 {
|
||||||
let turn = TurnID {
|
steps,
|
||||||
parent: map.get_l(src).dst_i,
|
req,
|
||||||
src,
|
cost,
|
||||||
dst,
|
uber_turns,
|
||||||
};
|
|
||||||
if map.maybe_get_t(turn).is_some() {
|
|
||||||
graph.add_edge(src, dst, turn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match petgraph::algo::astar(
|
/// Vehicle implementations often just calculate the sequence of roads. Turn that into
|
||||||
&graph,
|
/// PathStepV2 here.
|
||||||
req.start.lane(),
|
pub(crate) fn from_roads(
|
||||||
|l| l == req.end.lane(),
|
mut roads: Vec<DirectedRoadID>,
|
||||||
|(_, _, t)| {
|
req: PathRequest,
|
||||||
// Normally opportunistic lane-changing adjusts the path live, but that doesn't work
|
cost: Duration,
|
||||||
// near uber-turns. So still use some of the penalties here.
|
uber_turns: Vec<UberTurnV2>,
|
||||||
let (lt, lc, slow_lane) = map.get_t(*t).penalty(map);
|
map: &Map,
|
||||||
let mut extra_penalty = lt + lc;
|
) -> PathV2 {
|
||||||
if req.constraints == PathConstraints::Bike {
|
let mut steps = Vec::new();
|
||||||
extra_penalty = slow_lane;
|
for pair in roads.windows(2) {
|
||||||
}
|
steps.push(PathStepV2::Along(pair[0]));
|
||||||
// Always treat every lane/turn as at least cost 1; otherwise A* can't understand that
|
steps.push(PathStepV2::Movement(MovementID {
|
||||||
// a final path with 10 steps costs more than one with 5. The road-based pathfinding
|
from: pair[0],
|
||||||
// has already chosen the overall route; when we're picking individual lanes, the
|
to: pair[1],
|
||||||
// length of each lane along one road is going to be about the same.
|
parent: pair[0].dst_i(map),
|
||||||
let base = 1;
|
crosswalk: false,
|
||||||
base + extra_penalty
|
}));
|
||||||
},
|
|
||||||
|_| 0,
|
|
||||||
) {
|
|
||||||
Some((_, path)) => {
|
|
||||||
let mut steps = Vec::new();
|
|
||||||
for pair in path.windows(2) {
|
|
||||||
steps.push(PathStep::Lane(pair[0]));
|
|
||||||
// We don't need to look for this turn in the map; we know it exists.
|
|
||||||
steps.push(PathStep::Turn(TurnID {
|
|
||||||
parent: map.get_l(pair[0]).dst_i,
|
|
||||||
src: pair[0],
|
|
||||||
dst: pair[1],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
steps.push(PathStep::Lane(req.end.lane()));
|
|
||||||
assert_eq!(steps[0], PathStep::Lane(req.start.lane()));
|
|
||||||
let uber_turns = find_uber_turns(&steps, map, uber_turns_v2);
|
|
||||||
Ok(Path::new(map, steps, req, uber_turns))
|
|
||||||
}
|
}
|
||||||
None => bail!(
|
steps.push(PathStepV2::Along(roads.pop().unwrap()));
|
||||||
"path_v2_to_v1 found road-based path, but not a lane-based path matching it for {}",
|
PathV2::new(steps, req, cost, uber_turns)
|
||||||
req
|
}
|
||||||
),
|
|
||||||
|
/// The original PathRequest used to produce this path.
|
||||||
|
pub fn get_req(&self) -> &PathRequest {
|
||||||
|
&self.req
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All steps in this path.
|
||||||
|
pub fn get_steps(&self) -> &Vec<PathStepV2> {
|
||||||
|
&self.steps
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The time needed to perform this path. This time is not a lower bound; physically following
|
||||||
|
/// the path might be faster. This time incorporates costs like using sub-optimal lanes or
|
||||||
|
/// taking difficult turns.
|
||||||
|
pub fn get_cost(&self) -> Duration {
|
||||||
|
self.cost
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform a sequence of roads representing a path into the current lane-based path, by
|
||||||
|
/// picking particular lanes and turns to use.
|
||||||
|
pub fn to_v1(self, map: &Map) -> Result<Path> {
|
||||||
|
if self.req.constraints == PathConstraints::Pedestrian {
|
||||||
|
return self.to_v1_walking(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a somewhat brute-force method: run Dijkstra's algorithm on a graph of lanes and
|
||||||
|
// turns, but only build the graph along the path of roads we've already found. This handles
|
||||||
|
// arbitrary lookahead needed, and forces use of the original start/end lanes requested.
|
||||||
|
let mut graph = petgraph::graphmap::DiGraphMap::new();
|
||||||
|
for step in &self.steps {
|
||||||
|
if let PathStepV2::Movement(mvmnt) = step {
|
||||||
|
for src in mvmnt.from.lanes(self.req.constraints, map) {
|
||||||
|
for dst in mvmnt.to.lanes(self.req.constraints, map) {
|
||||||
|
let turn = TurnID {
|
||||||
|
parent: map.get_l(src).dst_i,
|
||||||
|
src,
|
||||||
|
dst,
|
||||||
|
};
|
||||||
|
if map.maybe_get_t(turn).is_some() {
|
||||||
|
graph.add_edge(src, dst, turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match petgraph::algo::astar(
|
||||||
|
&graph,
|
||||||
|
self.req.start.lane(),
|
||||||
|
|l| l == self.req.end.lane(),
|
||||||
|
|(_, _, t)| {
|
||||||
|
// Normally opportunistic lane-changing adjusts the path live, but that doesn't work
|
||||||
|
// near uber-turns. So still use some of the penalties here.
|
||||||
|
let (lt, lc, slow_lane) = map.get_t(*t).penalty(map);
|
||||||
|
let mut extra_penalty = lt + lc;
|
||||||
|
if self.req.constraints == PathConstraints::Bike {
|
||||||
|
extra_penalty = slow_lane;
|
||||||
|
}
|
||||||
|
// Always treat every lane/turn as at least cost 1; otherwise A* can't understand
|
||||||
|
// that a final path with 10 steps costs more than one with 5. The
|
||||||
|
// road-based pathfinding has already chosen the overall route; when
|
||||||
|
// we're picking individual lanes, the length of each lane along one
|
||||||
|
// road is going to be about the same.
|
||||||
|
let base = 1;
|
||||||
|
base + extra_penalty
|
||||||
|
},
|
||||||
|
|_| 0,
|
||||||
|
) {
|
||||||
|
Some((_, path)) => {
|
||||||
|
let mut steps = Vec::new();
|
||||||
|
for pair in path.windows(2) {
|
||||||
|
steps.push(PathStep::Lane(pair[0]));
|
||||||
|
// We don't need to look for this turn in the map; we know it exists.
|
||||||
|
steps.push(PathStep::Turn(TurnID {
|
||||||
|
parent: map.get_l(pair[0]).dst_i,
|
||||||
|
src: pair[0],
|
||||||
|
dst: pair[1],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
steps.push(PathStep::Lane(self.req.end.lane()));
|
||||||
|
assert_eq!(steps[0], PathStep::Lane(self.req.start.lane()));
|
||||||
|
let uber_turns = find_uber_turns(&steps, map, self.uber_turns);
|
||||||
|
Ok(Path::new(map, steps, self.req, uber_turns))
|
||||||
|
}
|
||||||
|
None => bail!(
|
||||||
|
"Can't transform a road-based path to a lane-based path for {}",
|
||||||
|
self.req
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_v1_walking(self, map: &Map) -> Result<Path> {
|
||||||
|
let mut steps = Vec::new();
|
||||||
|
for step in self.steps {
|
||||||
|
steps.push(match step {
|
||||||
|
PathStepV2::Along(r) => PathStep::Lane(r.must_get_sidewalk(map)),
|
||||||
|
PathStepV2::Contraflow(r) => PathStep::ContraflowLane(r.must_get_sidewalk(map)),
|
||||||
|
PathStepV2::Movement(mvmnt) => PathStep::Turn(TurnID {
|
||||||
|
src: mvmnt.from.must_get_sidewalk(map),
|
||||||
|
dst: mvmnt.to.must_get_sidewalk(map),
|
||||||
|
parent: mvmnt.parent,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Path::new(map, steps, self.req, Vec::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,11 +12,10 @@ use geom::{Distance, Duration};
|
|||||||
use crate::pathfind::ch::round;
|
use crate::pathfind::ch::round;
|
||||||
use crate::pathfind::node_map::{deserialize_nodemap, NodeMap};
|
use crate::pathfind::node_map::{deserialize_nodemap, NodeMap};
|
||||||
use crate::pathfind::uber_turns::{IntersectionCluster, UberTurnV2};
|
use crate::pathfind::uber_turns::{IntersectionCluster, UberTurnV2};
|
||||||
use crate::pathfind::v2::path_v2_to_v1;
|
|
||||||
use crate::pathfind::zone_cost;
|
use crate::pathfind::zone_cost;
|
||||||
use crate::{
|
use crate::{
|
||||||
DirectedRoadID, Direction, DrivingSide, LaneType, Map, MovementID, Path, PathConstraints,
|
DirectedRoadID, Direction, DrivingSide, LaneType, Map, MovementID, PathConstraints,
|
||||||
PathRequest, RoadID, RoutingParams, Traversable, TurnType,
|
PathRequest, PathV2, RoadID, RoutingParams, Traversable, TurnType,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -93,7 +92,7 @@ impl VehiclePathfinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<(Path, Duration)> {
|
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<PathV2> {
|
||||||
assert!(!map.get_l(req.start.lane()).is_walkable());
|
assert!(!map.get_l(req.start.lane()).is_walkable());
|
||||||
let mut calc = self
|
let mut calc = self
|
||||||
.path_calc
|
.path_calc
|
||||||
@ -120,14 +119,13 @@ impl VehiclePathfinder {
|
|||||||
road_steps.push(mvmnt.to);
|
road_steps.push(mvmnt.to);
|
||||||
}
|
}
|
||||||
road_steps.pop();
|
road_steps.pop();
|
||||||
// Also remember the uber-turn exists, so we can reconstruct it in
|
// Also remember the uber-turn exists.
|
||||||
// path_v2_to_v1.
|
|
||||||
uber_turns.push(self.uber_turns[ut].clone());
|
uber_turns.push(self.uber_turns[ut].clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let path = path_v2_to_v1(req, road_steps, uber_turns, map).ok()?;
|
let cost = Duration::seconds(raw_path.get_weight() as f64);
|
||||||
Some((path, Duration::seconds(raw_path.get_weight() as f64)))
|
Some(PathV2::from_roads(road_steps, req, cost, uber_turns, map))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_edits(&mut self, map: &Map) {
|
pub fn apply_edits(&mut self, map: &Map) {
|
||||||
|
@ -16,8 +16,8 @@ use crate::pathfind::node_map::{deserialize_nodemap, NodeMap};
|
|||||||
use crate::pathfind::vehicles::VehiclePathfinder;
|
use crate::pathfind::vehicles::VehiclePathfinder;
|
||||||
use crate::pathfind::zone_cost;
|
use crate::pathfind::zone_cost;
|
||||||
use crate::{
|
use crate::{
|
||||||
BusRoute, BusRouteID, BusStopID, DirectedRoadID, IntersectionID, Map, Path, PathConstraints,
|
BusRoute, BusRouteID, BusStopID, DirectedRoadID, IntersectionID, Map, MovementID,
|
||||||
PathRequest, PathStep, Position, Traversable,
|
PathConstraints, PathRequest, PathStepV2, PathV2, Position, Traversable,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -107,7 +107,7 @@ impl SidewalkPathfinder {
|
|||||||
self.graph = fast_paths::prepare_with_order(&input_graph, &node_ordering).unwrap();
|
self.graph = fast_paths::prepare_with_order(&input_graph, &node_ordering).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<(Path, Duration)> {
|
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<PathV2> {
|
||||||
if req.start.lane() == req.end.lane() {
|
if req.start.lane() == req.end.lane() {
|
||||||
return Some(one_step_walking_path(req, map));
|
return Some(one_step_walking_path(req, map));
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ impl SidewalkPathfinder {
|
|||||||
let nodes = self.nodes.translate(&raw_path);
|
let nodes = self.nodes.translate(&raw_path);
|
||||||
let steps = walking_path_to_steps(nodes, map);
|
let steps = walking_path_to_steps(nodes, map);
|
||||||
let cost = Duration::seconds(raw_path.get_weight() as f64);
|
let cost = Duration::seconds(raw_path.get_weight() as f64);
|
||||||
Some((Path::new(map, steps, req, Vec::new()), cost))
|
Some(PathV2::new(steps, req, cost, Vec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt the pathfinding and see if we should ride a bus. If so, says (stop1, optional stop
|
/// Attempt the pathfinding and see if we should ride a bus. If so, says (stop1, optional stop
|
||||||
@ -328,10 +328,10 @@ fn transit_input_graph(
|
|||||||
constraints: route.route_type,
|
constraints: route.route_type,
|
||||||
};
|
};
|
||||||
let maybe_driving_cost = match route.route_type {
|
let maybe_driving_cost = match route.route_type {
|
||||||
PathConstraints::Bus => bus_graph.pathfind(req, map).map(|(_, cost)| cost),
|
PathConstraints::Bus => bus_graph.pathfind(req, map).map(|p| p.get_cost()),
|
||||||
// We always use Dijkstra for trains
|
// We always use Dijkstra for trains
|
||||||
PathConstraints::Train => {
|
PathConstraints::Train => {
|
||||||
dijkstra::pathfind(req, map.routing_params(), map).map(|(_, cost)| cost)
|
dijkstra::pathfind(req, map.routing_params(), map).map(|p| p.get_cost())
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
@ -357,10 +357,10 @@ fn transit_input_graph(
|
|||||||
constraints: route.route_type,
|
constraints: route.route_type,
|
||||||
};
|
};
|
||||||
let maybe_driving_cost = match route.route_type {
|
let maybe_driving_cost = match route.route_type {
|
||||||
PathConstraints::Bus => bus_graph.pathfind(req, map).map(|(_, cost)| cost),
|
PathConstraints::Bus => bus_graph.pathfind(req, map).map(|p| p.get_cost()),
|
||||||
// We always use Dijkstra for trains
|
// We always use Dijkstra for trains
|
||||||
PathConstraints::Train => {
|
PathConstraints::Train => {
|
||||||
dijkstra::pathfind(req, map.routing_params(), map).map(|(_, cost)| cost)
|
dijkstra::pathfind(req, map.routing_params(), map).map(|p| p.get_cost())
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
@ -404,11 +404,11 @@ fn transit_input_graph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walking_path_to_steps(path: Vec<WalkingNode>, map: &Map) -> Vec<PathStep> {
|
pub fn walking_path_to_steps(path: Vec<WalkingNode>, map: &Map) -> Vec<PathStepV2> {
|
||||||
let mut steps: Vec<PathStep> = Vec::new();
|
let mut steps = Vec::new();
|
||||||
|
|
||||||
for pair in path.windows(2) {
|
for pair in path.windows(2) {
|
||||||
let (r1, l1_endpt) = match pair[0] {
|
let (r1, r1_endpt) = match pair[0] {
|
||||||
WalkingNode::SidewalkEndpoint(r, endpt) => (r, endpt),
|
WalkingNode::SidewalkEndpoint(r, endpt) => (r, endpt),
|
||||||
WalkingNode::RideBus(_) => unreachable!(),
|
WalkingNode::RideBus(_) => unreachable!(),
|
||||||
WalkingNode::LeaveMap(_) => unreachable!(),
|
WalkingNode::LeaveMap(_) => unreachable!(),
|
||||||
@ -419,52 +419,59 @@ pub fn walking_path_to_steps(path: Vec<WalkingNode>, map: &Map) -> Vec<PathStep>
|
|||||||
WalkingNode::LeaveMap(_) => unreachable!(),
|
WalkingNode::LeaveMap(_) => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let l1 = r1.must_get_sidewalk(map);
|
if r1 == r2 {
|
||||||
let l2 = r2.must_get_sidewalk(map);
|
if r1_endpt {
|
||||||
|
steps.push(PathStepV2::Contraflow(r1));
|
||||||
if l1 == l2 {
|
|
||||||
if l1_endpt {
|
|
||||||
steps.push(PathStep::ContraflowLane(l1));
|
|
||||||
} else {
|
} else {
|
||||||
steps.push(PathStep::Lane(l1));
|
steps.push(PathStepV2::Along(r1));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let i = {
|
let i = if r1_endpt {
|
||||||
let l = map.get_l(l1);
|
r1.dst_i(map)
|
||||||
if l1_endpt {
|
} else {
|
||||||
l.dst_i
|
r1.src_i(map)
|
||||||
} else {
|
|
||||||
l.src_i
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// Could assert the intersection matches (l2, l2_endpt).
|
// Could assert the intersection matches (r2, r2_endpt).
|
||||||
if let Some(turn) = map.get_turn_between(l1, l2, i) {
|
if map
|
||||||
steps.push(PathStep::Turn(turn));
|
.get_turn_between(r1.must_get_sidewalk(map), r2.must_get_sidewalk(map), i)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
steps.push(PathStepV2::Movement(MovementID {
|
||||||
|
from: r1,
|
||||||
|
to: r2,
|
||||||
|
parent: i,
|
||||||
|
crosswalk: true,
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
println!("walking_path_to_steps has a weird path:");
|
println!("walking_path_to_steps has a weird path:");
|
||||||
for s in &path {
|
for s in &path {
|
||||||
println!("- {:?}", s);
|
println!("- {:?}", s);
|
||||||
}
|
}
|
||||||
panic!("No turn from {} to {} at {}", l1, l2, i);
|
panic!(
|
||||||
|
"No turn from {} ({}) to {} ({}) at {}",
|
||||||
|
r1,
|
||||||
|
r1.must_get_sidewalk(map),
|
||||||
|
r2,
|
||||||
|
r2.must_get_sidewalk(map),
|
||||||
|
i
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't start or end a path in a turn; sim layer breaks.
|
// Don't start or end a path in a turn; sim layer breaks.
|
||||||
if let PathStep::Turn(t) = steps[0] {
|
if let PathStepV2::Movement(mvmnt) = steps[0] {
|
||||||
let lane = map.get_l(t.src);
|
if mvmnt.from.src_i(map) == mvmnt.parent {
|
||||||
if lane.src_i == t.parent {
|
steps.insert(0, PathStepV2::Contraflow(mvmnt.from));
|
||||||
steps.insert(0, PathStep::ContraflowLane(lane.id));
|
|
||||||
} else {
|
} else {
|
||||||
steps.insert(0, PathStep::Lane(lane.id));
|
steps.insert(0, PathStepV2::Along(mvmnt.from));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let PathStep::Turn(t) = steps.last().unwrap() {
|
if let PathStepV2::Movement(mvmnt) = steps.last().cloned().unwrap() {
|
||||||
let lane = map.get_l(t.dst);
|
if mvmnt.to.src_i(map) == mvmnt.parent {
|
||||||
if lane.src_i == t.parent {
|
steps.push(PathStepV2::Along(mvmnt.to));
|
||||||
steps.push(PathStep::Lane(lane.id));
|
|
||||||
} else {
|
} else {
|
||||||
steps.push(PathStep::ContraflowLane(lane.id));
|
steps.push(PathStepV2::Contraflow(mvmnt.to));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,15 +479,13 @@ pub fn walking_path_to_steps(path: Vec<WalkingNode>, map: &Map) -> Vec<PathStep>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Do we even need this at all?
|
// TODO Do we even need this at all?
|
||||||
pub fn one_step_walking_path(req: PathRequest, map: &Map) -> (Path, Duration) {
|
pub fn one_step_walking_path(req: PathRequest, map: &Map) -> PathV2 {
|
||||||
// Weird case, but it can happen for walking from a building path to a bus stop that're
|
// Weird case, but it can happen for walking from a building path to a bus stop that're
|
||||||
// actually at the same spot.
|
// actually at the same spot.
|
||||||
let steps = if req.start.dist_along() == req.end.dist_along() {
|
let step = if req.start.dist_along() <= req.end.dist_along() {
|
||||||
vec![PathStep::Lane(req.start.lane())]
|
PathStepV2::Along(map.get_l(req.start.lane()).get_directed_parent())
|
||||||
} else if req.start.dist_along() < req.end.dist_along() {
|
|
||||||
vec![PathStep::Lane(req.start.lane())]
|
|
||||||
} else {
|
} else {
|
||||||
vec![PathStep::ContraflowLane(req.start.lane())]
|
PathStepV2::Contraflow(map.get_l(req.start.lane()).get_directed_parent())
|
||||||
};
|
};
|
||||||
let mut cost = (req.start.dist_along() - req.end.dist_along()).abs()
|
let mut cost = (req.start.dist_along() - req.end.dist_along()).abs()
|
||||||
/ Traversable::Lane(req.start.lane()).max_speed_along(
|
/ Traversable::Lane(req.start.lane()).max_speed_along(
|
||||||
@ -491,5 +496,5 @@ pub fn one_step_walking_path(req: PathRequest, map: &Map) -> (Path, Duration) {
|
|||||||
if map.get_l(req.start.lane()).is_shoulder() {
|
if map.get_l(req.start.lane()).is_shoulder() {
|
||||||
cost = 2.0 * cost;
|
cost = 2.0 * cost;
|
||||||
}
|
}
|
||||||
(Path::new(map, steps, req, Vec::new()), cost)
|
PathV2::new(vec![step], req, cost, Vec::new())
|
||||||
}
|
}
|
||||||
|
@ -108,13 +108,13 @@ impl CapSimState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
match map.pathfind_avoiding_roads(path.get_req().clone(), avoid_roads) {
|
match map.pathfind_avoiding_roads(path.get_req().clone(), avoid_roads) {
|
||||||
Some(path) => CapResult::Reroute(path),
|
Ok(path) => CapResult::Reroute(path),
|
||||||
None => {
|
Err(err) => {
|
||||||
if let Some(delay) = self.delay_trips_instead_of_cancelling {
|
if let Some(delay) = self.delay_trips_instead_of_cancelling {
|
||||||
CapResult::Delay(delay)
|
CapResult::Delay(delay)
|
||||||
} else {
|
} else {
|
||||||
CapResult::Cancel {
|
CapResult::Cancel {
|
||||||
reason: format!("no path avoiding caps: {}", path.get_req()),
|
reason: err.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user