start revamping bus route data model for #190. routes are one-way. make

buses appear at the first stop and vanish at the last.

not regenerating anything yet
This commit is contained in:
Dustin Carlino 2020-07-15 13:15:32 -07:00
parent 0ad14d17ac
commit 91a9a9a1bc
20 changed files with 413 additions and 474 deletions

View File

@ -290,7 +290,7 @@ pub fn extract_osm(
},
);
}
} else if tags.is("type", "route_master") {
} else if tags.is("type", "route") {
map.bus_routes
.extend(extract_route(&tags, rel, &doc, &id_to_way, &map.gps_bounds));
} else if tags.is("type", "multipolygon") && tags.contains_key("amenity") {
@ -648,104 +648,87 @@ fn glue_to_boundary(result_pl: PolyLine, boundary: &Ring) -> Option<Polygon> {
}
fn extract_route(
master_tags: &Tags,
master_rel: &osm_xml::Relation,
tags: &Tags,
rel: &osm_xml::Relation,
doc: &osm_xml::OSM,
id_to_way: &HashMap<i64, Vec<Pt2D>>,
gps_bounds: &GPSBounds,
) -> Option<RawBusRoute> {
let route_name = master_tags.get("name")?.clone();
let is_bus = match master_tags.get("route_master")?.as_ref() {
let full_name = tags.get("name")?.clone();
let short_name = tags
.get("ref")
.cloned()
.unwrap_or_else(|| full_name.clone());
let is_bus = match tags.get("route")?.as_ref() {
"bus" => true,
"light_rail" => false,
x => {
println!(
"Skipping route {} of unknown type {}: {}",
route_name,
full_name,
x,
rel_url(master_rel.id)
rel_url(rel.id)
);
return None;
}
};
let mut directions = Vec::new();
for (_, route_member) in get_members(master_rel, doc) {
if let osm_xml::Reference::Relation(route_rel) = route_member {
let route_tags = tags_to_map(&route_rel.tags);
assert_eq!(route_tags.get("type"), Some(&"route".to_string()));
// Gather stops in order. Platforms may exist or not; match them up by name.
let mut stops = Vec::new();
let mut platforms = HashMap::new();
for (role, member) in get_members(&route_rel, doc) {
if role == "stop" {
if let osm_xml::Reference::Node(node) = member {
stops.push(RawBusStop {
name: tags_to_map(&node.tags)
.get("name")
.cloned()
.unwrap_or_else(|| format!("stop #{}", stops.len() + 1)),
vehicle_pos: Pt2D::from_gps(
LonLat::new(node.lon, node.lat),
gps_bounds,
),
ped_pos: None,
});
}
} else if role == "platform" {
let (platform_name, pt) = match member {
osm_xml::Reference::Node(node) => (
tags_to_map(&node.tags)
.get("name")
.cloned()
.unwrap_or_else(|| format!("stop #{}", platforms.len() + 1)),
Pt2D::from_gps(LonLat::new(node.lon, node.lat), gps_bounds),
),
osm_xml::Reference::Way(way) => (
tags_to_map(&way.tags)
.get("name")
.cloned()
.unwrap_or_else(|| format!("stop #{}", platforms.len() + 1)),
if let Some(ref pts) = id_to_way.get(&way.id) {
Pt2D::center(pts)
} else {
continue;
},
),
_ => continue,
};
platforms.insert(platform_name, pt);
}
}
for stop in &mut stops {
if let Some(pt) = platforms.remove(&stop.name) {
stop.ped_pos = Some(pt);
}
}
if stops.len() >= 2 {
directions.push(stops);
// Gather stops in order. Platforms may exist or not; match them up by name.
let mut stops = Vec::new();
let mut platforms = HashMap::new();
for (role, member) in get_members(rel, doc) {
if role == "stop" {
if let osm_xml::Reference::Node(node) = member {
stops.push(RawBusStop {
name: tags_to_map(&node.tags)
.get("name")
.cloned()
.unwrap_or_else(|| format!("stop #{}", stops.len() + 1)),
vehicle_pos: Pt2D::from_gps(LonLat::new(node.lon, node.lat), gps_bounds),
ped_pos: None,
});
}
} else if role == "platform" {
let (platform_name, pt) = match member {
osm_xml::Reference::Node(node) => (
tags_to_map(&node.tags)
.get("name")
.cloned()
.unwrap_or_else(|| format!("stop #{}", platforms.len() + 1)),
Pt2D::from_gps(LonLat::new(node.lon, node.lat), gps_bounds),
),
osm_xml::Reference::Way(way) => (
tags_to_map(&way.tags)
.get("name")
.cloned()
.unwrap_or_else(|| format!("stop #{}", platforms.len() + 1)),
if let Some(ref pts) = id_to_way.get(&way.id) {
Pt2D::center(pts)
} else {
continue;
},
),
_ => continue,
};
platforms.insert(platform_name, pt);
}
}
if directions.len() == 2 {
Some(RawBusRoute {
name: route_name,
is_bus,
osm_rel_id: master_rel.id,
// The direction is arbitrary right now
fwd_stops: directions.pop().unwrap(),
back_stops: directions.pop().unwrap(),
})
} else {
println!(
"Skipping route {} with {} sub-routes (only handling 2): {}",
route_name,
directions.len(),
rel_url(master_rel.id),
);
None
for stop in &mut stops {
if let Some(pt) = platforms.remove(&stop.name) {
stop.ped_pos = Some(pt);
}
}
if stops.is_empty() {
return None;
}
Some(RawBusRoute {
full_name,
short_name,
is_bus,
osm_rel_id: rel.id,
stops,
})
}
// Work around osm_xml's API, which shows the node/way/relation distinction twice. This returns

View File

@ -175,7 +175,7 @@ impl CommonState {
if let Some(r) = app.primary.sim.bus_route_id(c) {
osd.append_all(vec![
Line(" serving "),
Line(&map.get_br(r).name).fg(name_color),
Line(&map.get_br(r).full_name).fg(name_color),
]);
}
}
@ -200,7 +200,7 @@ impl CommonState {
let routes: BTreeSet<String> = map
.get_routes_serving_stop(bs)
.into_iter()
.map(|r| r.name.clone())
.map(|r| r.full_name.clone())
.collect();
list_names(&mut osd, |l| l.fg(name_color), routes);
}

View File

@ -26,12 +26,12 @@ pub fn stop(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusStopID)
for r in app.primary.map.get_routes_serving_stop(id) {
let buses = app.primary.sim.status_of_buses(r.id);
if buses.is_empty() {
rows.push(format!("Route {}: no buses running", r.name).draw_text(ctx));
rows.push(format!("Route {}: no buses running", r.full_name).draw_text(ctx));
} else {
rows.push(Btn::text_fg(format!("Route {}", r.name)).build_def(ctx, None));
rows.push(Btn::text_fg(format!("Route {}", r.full_name)).build_def(ctx, None));
details
.hyperlinks
.insert(format!("Route {}", r.name), Tab::BusStatus(buses[0].0));
.insert(format!("Route {}", r.full_name), Tab::BusStatus(buses[0].0));
}
let arrivals: Vec<(Time, CarID)> = all_arrivals
@ -110,7 +110,7 @@ fn bus_header(
Line(format!(
"{} (route {})",
id,
app.primary.map.get_br(route).name
app.primary.map.get_br(route).full_name
))
.small_heading()
.draw(ctx),
@ -138,12 +138,8 @@ fn delays_over_time(ctx: &mut EventCtx, app: &App, id: BusRouteID) -> Widget {
.bus_arrivals_over_time(app.primary.sim.time(), id);
let mut series = Vec::new();
for idx1 in 0..route.stops.len() {
let idx2 = if idx1 == route.stops.len() - 1 {
0
} else {
idx1 + 1
};
for idx1 in 0..route.stops.len() - 1 {
let idx2 = idx1 + 1;
series.push(Series {
label: format!("Stop {}->{}", idx1 + 1, idx2 + 1),
color: app.cs.rotating_color_plot(idx1),
@ -169,6 +165,7 @@ fn passenger_delay(ctx: &mut EventCtx, app: &App, details: &mut Details, id: Bus
.get_analytics()
.bus_passenger_delays(app.primary.sim.time(), id)
.collect::<BTreeMap<_, _>>();
// TODO I smell an off by one
for idx in 0..route.stops.len() {
col.push(Widget::row(vec![
format!("Stop {}", idx + 1).draw_text(ctx),
@ -202,11 +199,7 @@ fn passenger_delay(ctx: &mut EventCtx, app: &App, details: &mut Details, id: Bus
for (_, stop_idx, percent_next_stop) in app.primary.sim.status_of_buses(route.id) {
// TODO Line it up right in the middle of the line of text. This is probably a bit
// wrong.
let base_percent_y = if stop_idx == route.stops.len() - 1 {
0.0
} else {
(stop_idx as f64) / ((route.stops.len() - 1) as f64)
};
let base_percent_y = (stop_idx as f64) / ((route.stops.len() - 1) as f64);
batch.push(
Color::BLUE,
Circle::new(

View File

@ -192,22 +192,11 @@ impl ShowBusRoute {
}
let mut colorer = ColorDiscrete::new(app, vec![("route", app.cs.unzoomed_bus)]);
for (stop1, stop2) in
route
.stops
.iter()
.zip(route.stops.iter().skip(1))
.chain(std::iter::once((
route.stops.last().unwrap(),
&route.stops[0],
)))
{
let bs1 = map.get_bs(*stop1);
let bs2 = map.get_bs(*stop2);
for pair in route.stops.windows(2) {
for step in map
.pathfind(PathRequest {
start: bs1.driving_pos,
end: bs2.driving_pos,
start: map.get_bs(pair[0]).driving_pos,
end: map.get_bs(pair[1]).driving_pos,
constraints: route.route_type,
})
.unwrap()
@ -237,7 +226,7 @@ impl ShowBusRoute {
composite: Composite::new(Widget::col(vec![
Widget::row(vec![
Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
Line(&route.name).draw(ctx),
Line(&route.full_name).draw(ctx),
Btn::plaintext("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),

View File

@ -72,7 +72,7 @@ impl BusRoutes {
.map
.all_bus_routes()
.iter()
.map(|r| r.name.clone())
.map(|r| r.full_name.clone())
.collect();
// TODO Sort first by length, then lexicographically
routes.sort();
@ -106,7 +106,7 @@ impl State for BusRoutes {
if buses.is_empty() {
Transition::Push(msg(
"No buses running",
vec![format!("Sorry, no buses for route {} running", r.name)],
vec![format!("Sorry, no buses for route {} running", r.full_name)],
))
} else {
Transition::PopWithData(Box::new(move |state, ctx, app| {

View File

@ -1,17 +1,17 @@
mod bridges;
mod buildings;
mod bus_stops;
pub mod initial;
mod remove_disconnected;
pub mod traffic_signals;
mod transit;
pub mod turns;
use crate::pathfind::Pathfinder;
use crate::raw::{OriginalIntersection, OriginalRoad, RawMap};
use crate::{
connectivity, osm, Area, AreaID, BusRouteID, ControlStopSign, ControlTrafficSignal,
Intersection, IntersectionID, IntersectionType, Lane, LaneID, Map, MapEdits, PathConstraints,
Position, Road, RoadID, Zone,
connectivity, osm, Area, AreaID, ControlStopSign, ControlTrafficSignal, Intersection,
IntersectionID, IntersectionType, Lane, LaneID, Map, MapEdits, PathConstraints, Position, Road,
RoadID, Zone,
};
use abstutil::Timer;
use enumset::EnumSet;
@ -320,44 +320,9 @@ impl Map {
map.pathfinder = Some(Pathfinder::new_without_transit(&map, timer));
timer.stop("setup (most of) Pathfinder");
{
// Turn the two directions of each route into one loop. Need to do something better
// with borders later.
for r in &mut raw.bus_routes {
r.fwd_stops.extend(r.back_stops.drain(..));
}
let (stops, routes) = bus_stops::make_bus_stops(&mut map, &raw.bus_routes, timer);
map.bus_stops = stops;
timer.start_iter("verify bus routes are connected", routes.len());
for mut r in routes {
timer.next();
if r.stops.is_empty() {
continue;
}
if bus_stops::fix_bus_route(&map, &mut r) {
r.id = BusRouteID(map.bus_routes.len());
map.bus_routes.push(r);
} else {
timer.warn(format!(
"Skipping route {} due to connectivity problems",
r.name
));
}
}
// Remove orphaned bus stops. This messes up the BusStopID indexing.
for id in map
.bus_stops
.keys()
.filter(|id| map.get_routes_serving_stop(**id).is_empty())
.cloned()
.collect::<Vec<_>>()
{
map.bus_stops.remove(&id);
map.lanes[id.sidewalk.0].bus_stops.remove(&id);
}
transit::make_stops_and_routes(&mut map, &raw.bus_routes, timer);
for id in map.bus_stops.keys() {
assert!(!map.get_routes_serving_stop(*id).is_empty());
}
timer.start("setup rest of Pathfinder (walking with transit)");

View File

@ -6,6 +6,101 @@ use crate::{
use abstutil::Timer;
use geom::{Distance, HashablePt2D};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::error::Error;
pub fn make_stops_and_routes(map: &mut Map, raw_routes: &Vec<RawBusRoute>, timer: &mut Timer) {
timer.start("make transit stops and routes");
let matcher = Matcher::new(raw_routes, map, timer);
// TODO I'm assuming the vehicle_pos <-> driving_pos relation is one-to-one...
let mut pt_to_stop: BTreeMap<(Position, Position), BusStopID> = BTreeMap::new();
for r in raw_routes {
let mut stops = Vec::new();
let mut ok = true;
for stop in &r.stops {
if let Some((sidewalk_pos, driving_pos)) = matcher.lookup(r.is_bus, stop, map) {
// Create a new bus stop if needed.
let stop_id = if let Some(id) = pt_to_stop.get(&(sidewalk_pos, driving_pos)) {
*id
} else {
let id = BusStopID {
sidewalk: sidewalk_pos.lane(),
idx: map.get_l(sidewalk_pos.lane()).bus_stops.len(),
};
pt_to_stop.insert((sidewalk_pos, driving_pos), id);
map.lanes[sidewalk_pos.lane().0].bus_stops.insert(id);
map.bus_stops.insert(
id,
BusStop {
id,
name: stop.name.clone(),
driving_pos,
sidewalk_pos,
is_train_stop: !r.is_bus,
},
);
id
};
stops.push(stop_id);
} else {
timer.warn(format!(
"Couldn't match stop {} for route {} ({})",
stop.name,
r.full_name,
rel_url(r.osm_rel_id)
));
ok = false;
break;
}
}
if !ok {
continue;
}
// Make sure the stops are connected
let route_type = if r.is_bus {
PathConstraints::Bus
} else {
PathConstraints::Train
};
let mut ok = true;
for pair in stops.windows(2) {
if let Err(err) = check_stops(route_type, pair[0], pair[1], map) {
timer.warn(format!(
"Route {} ({}) disconnected: {}",
r.full_name,
rel_url(r.osm_rel_id),
err
));
ok = false;
break;
}
}
if ok {
map.bus_routes.push(BusRoute {
id: BusRouteID(map.bus_routes.len()),
full_name: r.full_name.clone(),
short_name: r.short_name.clone(),
stops,
route_type,
});
}
}
// Remove orphaned bus stops. This messes up the BusStopID indexing.
for id in map
.bus_stops
.keys()
.filter(|id| map.get_routes_serving_stop(**id).is_empty())
.cloned()
.collect::<Vec<_>>()
{
map.bus_stops.remove(&id);
map.lanes[id.sidewalk.0].bus_stops.remove(&id);
}
timer.stop("make transit stops and routes");
}
struct Matcher {
sidewalk_pts: HashMap<HashablePt2D, Position>,
@ -14,13 +109,13 @@ struct Matcher {
}
impl Matcher {
fn new(bus_routes: &Vec<RawBusRoute>, map: &Map, timer: &mut Timer) -> Matcher {
fn new(routes: &Vec<RawBusRoute>, map: &Map, timer: &mut Timer) -> Matcher {
// Match all of the points to an exact position along a lane.
let mut lookup_sidewalk_pts = HashSet::new();
let mut lookup_bus_pts = HashSet::new();
let mut lookup_light_rail_pts = HashSet::new();
for r in bus_routes {
for stop in &r.fwd_stops {
for r in routes {
for stop in &r.stops {
if r.is_bus {
lookup_bus_pts.insert(stop.vehicle_pos.to_hashable());
} else {
@ -107,114 +202,40 @@ impl Matcher {
}
}
pub fn make_bus_stops(
map: &mut Map,
bus_routes: &Vec<RawBusRoute>,
timer: &mut Timer,
) -> (BTreeMap<BusStopID, BusStop>, Vec<BusRoute>) {
timer.start("make bus stops");
let matcher = Matcher::new(bus_routes, map, timer);
// TODO I'm assuming the vehicle_pos <-> driving_pos relation is one-to-one...
let mut pt_to_stop: BTreeMap<(Position, Position), BusStopID> = BTreeMap::new();
let mut bus_stops: BTreeMap<BusStopID, BusStop> = BTreeMap::new();
let mut routes: Vec<BusRoute> = Vec::new();
for r in bus_routes {
let mut stops = Vec::new();
for stop in &r.fwd_stops {
if let Some((sidewalk_pos, driving_pos)) = matcher.lookup(r.is_bus, stop, map) {
// Create a new bus stop if needed.
let stop_id = if let Some(id) = pt_to_stop.get(&(sidewalk_pos, driving_pos)) {
*id
} else {
let id = BusStopID {
sidewalk: sidewalk_pos.lane(),
idx: map.get_l(sidewalk_pos.lane()).bus_stops.len(),
};
pt_to_stop.insert((sidewalk_pos, driving_pos), id);
map.lanes[sidewalk_pos.lane().0].bus_stops.insert(id);
bus_stops.insert(
id,
BusStop {
id,
name: stop.name.clone(),
driving_pos,
sidewalk_pos,
is_train_stop: !r.is_bus,
},
);
id
};
stops.push(stop_id);
}
}
routes.push(BusRoute {
id: BusRouteID(routes.len()),
name: r.name.clone(),
stops,
route_type: if r.is_bus {
PathConstraints::Bus
} else {
PathConstraints::Train
},
});
}
timer.stop("make bus stops");
(bus_stops, routes)
}
// Trim out stops if needed; map borders sometimes mean some paths don't work.
pub fn fix_bus_route(map: &Map, r: &mut BusRoute) -> bool {
let mut stops = Vec::new();
for stop in r.stops.drain(..) {
if stops.is_empty() {
stops.push(stop);
} else {
if check_stops(&r.name, r.route_type, *stops.last().unwrap(), stop, map) {
stops.push(stop);
}
}
}
// Don't forget the last and first -- except temporarily for light rail!
if r.route_type == PathConstraints::Bus {
while stops.len() >= 2 {
if check_stops(&r.name, r.route_type, *stops.last().unwrap(), stops[0], map) {
break;
}
// TODO Or the front one
stops.pop();
}
}
r.stops = stops;
r.stops.len() >= 2
}
fn check_stops(
route: &str,
constraints: PathConstraints,
stop1: BusStopID,
stop2: BusStopID,
map: &Map,
) -> bool {
) -> Result<(), Box<dyn Error>> {
let start = map.get_bs(stop1).driving_pos;
let end = map.get_bs(stop2).driving_pos;
if start.lane() == end.lane() && start.dist_along() > end.dist_along() {
println!(
"Route {} has two bus stops seemingly out of order somewhere on {}",
route,
return Err(format!(
"Two stops seemingly out of order somewhere on {}",
map.get_parent(start.lane()).orig_id
);
return false;
)
.into());
}
map.pathfind(PathRequest {
start,
end,
constraints,
})
.is_some()
if map
.pathfind(PathRequest {
start,
end,
constraints,
})
.is_some()
{
return Ok(());
}
Err(format!(
"No path between stop on {} and {}",
map.get_parent(start.lane()).orig_id,
map.get_parent(end.lane()).orig_id
)
.into())
}
fn rel_url(id: i64) -> String {
format!("https://www.openstreetmap.org/relation/{}", id)
}

View File

@ -400,7 +400,7 @@ impl Map {
}
pub fn get_bus_route(&self, name: &str) -> Option<&BusRoute> {
self.bus_routes.iter().find(|r| r.name == name)
self.bus_routes.iter().find(|r| r.full_name == name)
}
pub fn get_routes_serving_stop(&self, stop: BusStopID) -> Vec<&BusRoute> {

View File

@ -46,7 +46,8 @@ pub struct BusStop {
#[derive(Serialize, Deserialize, Debug)]
pub struct BusRoute {
pub id: BusRouteID,
pub name: String,
pub full_name: String,
pub short_name: String,
pub stops: Vec<BusStopID>,
pub route_type: PathConstraints,
}

View File

@ -146,6 +146,15 @@ impl Path {
}
}
pub fn one_step(l: LaneID, map: &Map) -> Path {
Path::new(
map,
vec![PathStep::Lane(l)],
map.get_l(l).length(),
Vec::new(),
)
}
// Only used for weird serialization magic.
pub fn dummy() -> Path {
Path {
@ -596,7 +605,7 @@ impl Pathfinder {
timer.stop("prepare pathfinding for trains");
timer.start("prepare pathfinding for pedestrians");
let walking_graph = SidewalkPathfinder::new(map, false, &bus_graph);
let walking_graph = SidewalkPathfinder::new(map, false, &bus_graph, &train_graph);
timer.stop("prepare pathfinding for pedestrians");
Pathfinder {
@ -610,7 +619,12 @@ impl Pathfinder {
}
pub fn setup_walking_with_transit(&mut self, map: &Map) {
self.walking_with_transit_graph = Some(SidewalkPathfinder::new(map, true, &self.bus_graph));
self.walking_with_transit_graph = Some(SidewalkPathfinder::new(
map,
true,
&self.bus_graph,
&self.train_graph,
));
}
pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<Path> {
@ -883,14 +897,15 @@ impl Pathfinder {
// Can't edit anything related to trains
timer.start("apply edits to pedestrian pathfinding");
self.walking_graph.apply_edits(map, &self.bus_graph);
self.walking_graph
.apply_edits(map, &self.bus_graph, &self.train_graph);
timer.stop("apply edits to pedestrian pathfinding");
timer.start("apply edits to pedestrian using transit pathfinding");
self.walking_with_transit_graph
.as_mut()
.unwrap()
.apply_edits(map, &self.bus_graph);
.apply_edits(map, &self.bus_graph, &self.train_graph);
timer.stop("apply edits to pedestrian using transit pathfinding");
}
}

View File

@ -36,7 +36,12 @@ impl WalkingNode {
}
impl SidewalkPathfinder {
pub fn new(map: &Map, use_transit: bool, bus_graph: &VehiclePathfinder) -> SidewalkPathfinder {
pub fn new(
map: &Map,
use_transit: bool,
bus_graph: &VehiclePathfinder,
train_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() {
@ -52,7 +57,13 @@ impl SidewalkPathfinder {
}
}
let graph = fast_paths::prepare(&make_input_graph(map, &nodes, use_transit, bus_graph));
let graph = fast_paths::prepare(&make_input_graph(
map,
&nodes,
use_transit,
bus_graph,
train_graph,
));
SidewalkPathfinder {
graph,
nodes,
@ -61,10 +72,16 @@ impl SidewalkPathfinder {
}
}
pub fn apply_edits(&mut self, map: &Map, bus_graph: &VehiclePathfinder) {
pub fn apply_edits(
&mut self,
map: &Map,
bus_graph: &VehiclePathfinder,
train_graph: &VehiclePathfinder,
) {
// The NodeMap is all sidewalks and bus stops -- it won't change. So we can also reuse the
// node ordering.
let input_graph = make_input_graph(map, &self.nodes, self.use_transit, bus_graph);
let input_graph =
make_input_graph(map, &self.nodes, self.use_transit, bus_graph, train_graph);
let node_ordering = self.graph.get_node_ordering();
self.graph = fast_paths::prepare_with_order(&input_graph, &node_ordering).unwrap();
}
@ -126,6 +143,7 @@ fn make_input_graph(
nodes: &NodeMap<WalkingNode>,
use_transit: bool,
bus_graph: &VehiclePathfinder,
train_graph: &VehiclePathfinder,
) -> InputGraph {
let mut input_graph = InputGraph::new();
@ -181,38 +199,30 @@ fn make_input_graph(
// Connect each adjacent stop along a route, with the cost based on how long it'll take a
// bus to drive between the stops. Optimistically assume no waiting time at a stop.
for route in map.all_bus_routes() {
// TODO Gotta connect the stops properly first
if route.route_type == PathConstraints::Train {
continue;
}
for (stop1, stop2) in
route
.stops
.iter()
.zip(route.stops.iter().skip(1))
.chain(std::iter::once((
route.stops.last().unwrap(),
&route.stops[0],
)))
{
if let Some((_, driving_cost)) = bus_graph.pathfind(
for pair in route.stops.windows(2) {
let (stop1, stop2) = (map.get_bs(pair[0]), map.get_bs(pair[1]));
let graph = match route.route_type {
PathConstraints::Bus => bus_graph,
PathConstraints::Train => train_graph,
_ => unreachable!(),
};
if let Some((_, driving_cost)) = graph.pathfind(
&PathRequest {
start: map.get_bs(*stop1).driving_pos,
end: map.get_bs(*stop2).driving_pos,
constraints: PathConstraints::Bus,
start: stop1.driving_pos,
end: stop2.driving_pos,
constraints: route.route_type,
},
map,
) {
input_graph.add_edge(
nodes.get(WalkingNode::RideBus(*stop1)),
nodes.get(WalkingNode::RideBus(*stop2)),
nodes.get(WalkingNode::RideBus(stop1.id)),
nodes.get(WalkingNode::RideBus(stop2.id)),
driving_cost,
);
} else {
panic!(
"No bus route from {} to {} now! Prevent this edit",
map.get_bs(*stop1).driving_pos,
map.get_bs(*stop2).driving_pos
"No bus route from {} to {} now for {}! Prevent this edit",
stop1.driving_pos, stop2.driving_pos, route.full_name,
);
}
}

View File

@ -429,12 +429,12 @@ impl DrivingSide {
#[derive(Debug, Serialize, Deserialize)]
pub struct RawBusRoute {
pub name: String,
pub full_name: String,
pub short_name: String,
pub osm_rel_id: i64,
// If not, light rail
pub is_bus: bool,
pub fwd_stops: Vec<RawBusStop>,
pub back_stops: Vec<RawBusStop>,
pub stops: Vec<RawBusStop>,
}
#[derive(Debug, Serialize, Deserialize)]

View File

@ -78,8 +78,10 @@ impl TripPhaseType {
TripPhaseType::Walking => "walking".to_string(),
TripPhaseType::Biking => "biking".to_string(),
TripPhaseType::Parking => "parking".to_string(),
TripPhaseType::WaitingForBus(r, _) => format!("waiting for bus {}", map.get_br(r).name),
TripPhaseType::RidingBus(r, _, _) => format!("riding bus {}", map.get_br(r).name),
TripPhaseType::WaitingForBus(r, _) => {
format!("waiting for bus {}", map.get_br(r).full_name)
}
TripPhaseType::RidingBus(r, _, _) => format!("riding bus {}", map.get_br(r).full_name),
TripPhaseType::Aborted => "trip aborted due to some bug".to_string(),
TripPhaseType::Finished => "trip finished".to_string(),
TripPhaseType::DelayedStart => "delayed by previous trip taking too long".to_string(),

View File

@ -96,7 +96,7 @@ impl Scenario {
if let Some(ref routes) = self.only_seed_buses {
for route in map.all_bus_routes() {
if routes.contains(&route.name) {
if routes.contains(&route.full_name) {
sim.seed_bus_route(route, map, timer);
}
}

View File

@ -214,7 +214,7 @@ impl Car {
CarState::Unparking(_, _, _) => CarStatus::Parked,
CarState::Parking(_, _, _) => CarStatus::Parked,
// Changing color for idling buses is helpful
CarState::Idling(_, _) => CarStatus::Parked,
CarState::IdlingAtStop(_, _) => CarStatus::Parked,
},
on: self.router.head(),
partly_on,
@ -223,8 +223,8 @@ impl Car {
{
Some(
map.get_br(transit.bus_route(self.vehicle.id))
.name
.to_string(),
.short_name
.clone(),
)
} else {
None
@ -249,7 +249,7 @@ pub enum CarState {
// Where's the front of the car while this is happening?
Unparking(Distance, ParkingSpot, TimeInterval),
Parking(Distance, ParkingSpot, TimeInterval),
Idling(Distance, TimeInterval),
IdlingAtStop(Distance, TimeInterval),
}
impl CarState {
@ -260,7 +260,7 @@ impl CarState {
CarState::WaitingToAdvance { .. } => unreachable!(),
CarState::Unparking(_, _, ref time_int) => time_int.end,
CarState::Parking(_, _, ref time_int) => time_int.end,
CarState::Idling(_, ref time_int) => time_int.end,
CarState::IdlingAtStop(_, ref time_int) => time_int.end,
}
}
}

View File

@ -3,8 +3,8 @@ use crate::mechanics::Queue;
use crate::{
ActionAtEnd, AgentID, AgentProperties, CarID, Command, CreateCar, DistanceInterval,
DrawCarInput, Event, IntersectionSimState, ParkedCar, ParkingSimState, ParkingSpot, PersonID,
Scheduler, TimeInterval, TransitSimState, TripManager, UnzoomedAgent, Vehicle, VehicleType,
WalkingSimState, FOLLOWING_DISTANCE,
Scheduler, TimeInterval, TransitSimState, TripManager, UnzoomedAgent, Vehicle, WalkingSimState,
FOLLOWING_DISTANCE,
};
use abstutil::{deserialize_btreemap, serialize_btreemap};
use geom::{Distance, Duration, PolyLine, Time};
@ -171,7 +171,7 @@ impl DrivingSimState {
//
// Crossing -> Queued or WaitingToAdvance
// Unparking -> Crossing
// Idling -> Crossing
// IdlingAtStop -> Crossing
// Queued -> last step handling (Parking or done)
// WaitingToAdvance -> try to advance to the next step of the path
// Parking -> done
@ -284,8 +284,8 @@ impl DrivingSimState {
car.state = car.crossing_state(front, now, map);
scheduler.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));
}
CarState::Idling(dist, _) => {
car.router = transit.bus_departed_from_stop(car.vehicle.id);
CarState::IdlingAtStop(dist, _) => {
car.router = transit.bus_departed_from_stop(car.vehicle.id, map);
self.events
.push(Event::PathAmended(car.router.get_path().clone()));
car.state = car.crossing_state(dist, now, map);
@ -325,7 +325,7 @@ impl DrivingSimState {
CarState::Crossing(_, _)
| CarState::Unparking(_, _, _)
| CarState::Parking(_, _, _)
| CarState::Idling(_, _) => {}
| CarState::IdlingAtStop(_, _) => {}
}
}
}
@ -429,7 +429,7 @@ impl DrivingSimState {
match car.state {
CarState::Crossing(_, _)
| CarState::Unparking(_, _, _)
| CarState::Idling(_, _)
| CarState::IdlingAtStop(_, _)
| CarState::WaitingToAdvance { .. } => unreachable!(),
CarState::Queued { blocked_since } => {
match car.router.maybe_handle_end(
@ -514,15 +514,10 @@ impl DrivingSimState {
map,
);
car.total_blocked_time += now - blocked_since;
// TODO Light rail routes are all disconnected. For now, just sit forever
// after making one stop.
let wait_at_stop = if car.vehicle.vehicle_type == VehicleType::Train {
Duration::hours(24 * 30)
} else {
TIME_TO_WAIT_AT_STOP
};
car.state =
CarState::Idling(our_dist, TimeInterval::new(now, now + wait_at_stop));
car.state = CarState::IdlingAtStop(
our_dist,
TimeInterval::new(now, now + TIME_TO_WAIT_AT_STOP),
);
scheduler
.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));
true
@ -673,7 +668,7 @@ impl DrivingSimState {
// They weren't blocked
CarState::Unparking(_, _, _)
| CarState::Parking(_, _, _)
| CarState::Idling(_, _) => {}
| CarState::IdlingAtStop(_, _) => {}
CarState::WaitingToAdvance { .. } => unreachable!(),
}
}
@ -813,7 +808,7 @@ impl DrivingSimState {
CarState::Crossing(_, _)
| CarState::Unparking(_, _, _)
| CarState::Parking(_, _, _)
| CarState::Idling(_, _) => {}
| CarState::IdlingAtStop(_, _) => {}
}
}
} else {

View File

@ -150,7 +150,7 @@ impl Queue {
}
CarState::Unparking(front, _, _) => front,
CarState::Parking(front, _, _) => front,
CarState::Idling(front, _) => front,
CarState::IdlingAtStop(front, _) => front,
};
result.push((*id, front));
@ -274,7 +274,7 @@ fn dump_cars(
CarState::Parking(_, _, ref time_int) => {
println!(" Parking during {} .. {}", time_int.start, time_int.end);
}
CarState::Idling(_, ref time_int) => {
CarState::IdlingAtStop(_, ref time_int) => {
println!(" Idling during {} .. {}", time_int.start, time_int.end);
}
}

View File

@ -4,8 +4,8 @@ use crate::{
};
use geom::Distance;
use map_model::{
BuildingID, IntersectionID, Map, Path, PathConstraints, PathRequest, PathStep, Position,
Traversable, TurnID, TurnType,
BuildingID, IntersectionID, LaneID, Map, Path, PathConstraints, PathRequest, PathStep,
Position, Traversable, TurnID, TurnType,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
@ -57,6 +57,16 @@ impl Router {
goal: Goal::EndAtBorder { end_dist, i },
}
}
pub fn vanish_bus(l: LaneID, map: &Map) -> Router {
let lane = map.get_l(l);
Router {
path: Path::one_step(l, map),
goal: Goal::EndAtBorder {
end_dist: lane.length(),
i: lane.dst_i,
},
}
}
pub fn park_near(path: Path, bldg: BuildingID) -> Router {
Router {

View File

@ -13,7 +13,7 @@ use geom::{Distance, Duration, PolyLine, Pt2D, Speed, Time};
use instant::Instant;
use map_model::{
BuildingID, BusRoute, BusRouteID, IntersectionID, LaneID, Map, ParkingLotID, Path,
PathConstraints, PathRequest, PathStep, Position, RoadID, Traversable,
PathConstraints, PathRequest, Position, RoadID, Traversable,
};
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize};
@ -227,80 +227,65 @@ impl Sim {
self.parking.add_parked_car(ParkedCar { vehicle, spot });
}
pub fn seed_bus_route(&mut self, route: &BusRoute, map: &Map, timer: &mut Timer) -> Vec<CarID> {
let mut results: Vec<CarID> = Vec::new();
// Try to spawn just ONE bus anywhere.
// TODO Be more realistic. One bus per stop is too much, one is too little.
for (next_stop_idx, req, mut path, end_dist) in
self.transit.create_empty_route(route, map).into_iter()
{
// For now, no desire for randomness. Caller can pass in list of specs if that ever
// changes.
let (vehicle_type, length) = match route.route_type {
PathConstraints::Bus => (VehicleType::Bus, BUS_LENGTH),
PathConstraints::Train => (VehicleType::Train, LIGHT_RAIL_LENGTH),
_ => unreachable!(),
};
let vehicle = VehicleSpec {
vehicle_type,
length,
max_speed: None,
}
.make(CarID(self.trips.new_car_id(), vehicle_type), None);
let id = vehicle.id;
loop {
if path.is_empty() {
timer.warn(format!(
"Giving up on seeding a bus headed towards stop {} of {} ({})",
next_stop_idx, route.name, route.id
));
break;
}
let start_lane = if let PathStep::Lane(l) = path.current_step() {
l
} else {
path.shift(map);
// TODO Technically should update request, but it shouldn't matter
continue;
};
if map.get_l(start_lane).length() < vehicle.length {
path.shift(map);
// TODO Technically should update request, but it shouldn't matter
continue;
}
// Bypass some layers of abstraction that don't make sense for buses.
if self.driving.start_car_on_lane(
self.time,
CreateCar {
start_dist: vehicle.length,
vehicle: vehicle.clone(),
req: req.clone(),
router: Router::follow_bus_route(path.clone(), end_dist),
maybe_parked_car: None,
trip_and_person: None,
},
map,
&self.intersections,
&self.parking,
&mut self.scheduler,
) {
self.transit.bus_created(id, route.id, next_stop_idx);
self.analytics.record_demand(&path, map);
results.push(id);
return results;
} else {
path.shift(map);
}
}
// TODO Change this to be a periodic "start a bus at stop1 based on a schedule"
pub(crate) fn seed_bus_route(&mut self, route: &BusRoute, map: &Map, timer: &mut Timer) {
if route.stops.is_empty() {
panic!("{} has no stops", route.full_name);
}
if results.is_empty() {
// TODO Bigger failure
timer.warn(format!("Failed to make ANY buses for {}!", route.name));
// TODO This'll be valid when we have borders too
if route.stops.len() == 1 {
timer.error(format!("{} only has one stop", route.full_name));
return;
}
// Spawn one bus from stop1->stop2.
let (req, path) = self.transit.create_empty_route(route, map);
// For now, no desire for randomness. Caller can pass in list of specs if that ever
// changes.
let (vehicle_type, length) = match route.route_type {
PathConstraints::Bus => (VehicleType::Bus, BUS_LENGTH),
PathConstraints::Train => (VehicleType::Train, LIGHT_RAIL_LENGTH),
_ => unreachable!(),
};
let vehicle = VehicleSpec {
vehicle_type,
length,
max_speed: None,
}
.make(CarID(self.trips.new_car_id(), vehicle_type), None);
let id = vehicle.id;
let start = req.start.lane();
if map.get_l(start).length() < vehicle.length {
timer.error(format!("Can't start a bus on {}, too short", start));
return;
}
// Bypass some layers of abstraction that don't make sense for buses.
if self.driving.start_car_on_lane(
self.time,
CreateCar {
start_dist: vehicle.length,
vehicle,
router: Router::follow_bus_route(path.clone(), req.end.dist_along()),
req,
maybe_parked_car: None,
trip_and_person: None,
},
map,
&self.intersections,
&self.parking,
&mut self.scheduler,
) {
self.transit.bus_created(id, route.id);
self.analytics.record_demand(&path, map);
} else {
timer.error(format!(
"Can't start a bus on {}, something's in the way",
start
));
}
results
}
pub fn set_name(&mut self, name: String) {
@ -938,7 +923,7 @@ impl Sim {
vec![
(
"Route".to_string(),
map.get_br(self.transit.bus_route(car)).name.clone(),
map.get_br(self.transit.bus_route(car)).full_name.clone(),
),
("Passengers".to_string(), passengers.len().to_string()),
]

View File

@ -3,29 +3,25 @@ use crate::{
WalkingSimState,
};
use abstutil::{deserialize_btreemap, serialize_btreemap};
use geom::{Distance, Time};
use map_model::{
BusRoute, BusRouteID, BusStopID, Map, Path, PathConstraints, PathRequest, Position,
};
use geom::Time;
use map_model::{BusRoute, BusRouteID, BusStopID, Map, Path, PathRequest, Position};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
// These index stops along a route, not stops along a single sidewalk.
type StopIdx = usize;
#[derive(Serialize, Deserialize, PartialEq, Clone)]
struct StopForRoute {
struct Stop {
id: BusStopID,
driving_pos: Position,
req: PathRequest,
path_to_next_stop: Path,
next_stop_idx: StopIdx,
next_stop: Option<(PathRequest, Path)>,
}
#[derive(Serialize, Deserialize, PartialEq, Clone)]
struct Route {
stops: Vec<StopForRoute>,
buses: Vec<CarID>,
stops: Vec<Stop>,
buses: BTreeSet<CarID>,
}
#[derive(Serialize, Deserialize, PartialEq, Clone)]
@ -76,83 +72,61 @@ impl TransitSimState {
}
}
// Returns (next stop, first path, end distance for next stop) for all of the stops in the
// route.
pub fn create_empty_route(
&mut self,
bus_route: &BusRoute,
map: &Map,
) -> Vec<(StopIdx, PathRequest, Path, Distance)> {
// Returns the path and end distance for stop1->stop2.
pub fn create_empty_route(&mut self, bus_route: &BusRoute, map: &Map) -> (PathRequest, Path) {
assert!(bus_route.stops.len() > 1);
let mut stops = Vec::new();
for (idx, stop1_id) in bus_route.stops.iter().enumerate() {
let stop1 = map.get_bs(*stop1_id);
let stop2_idx = if idx + 1 == bus_route.stops.len() {
0
} else {
idx + 1
};
if idx == bus_route.stops.len() - 1 {
stops.push(Stop {
id: stop1.id,
driving_pos: stop1.driving_pos,
next_stop: None,
});
continue;
}
let req = PathRequest {
start: stop1.driving_pos,
end: map.get_bs(bus_route.stops[stop2_idx]).driving_pos,
end: map.get_bs(bus_route.stops[idx + 1]).driving_pos,
constraints: bus_route.route_type,
};
if let Some(path) = map.pathfind(req.clone()) {
stops.push(StopForRoute {
id: *stop1_id,
if path.is_empty() {
panic!("Empty path between stops?! {}", req);
}
stops.push(Stop {
id: stop1.id,
driving_pos: stop1.driving_pos,
req,
path_to_next_stop: path,
next_stop_idx: stop2_idx,
next_stop: Some((req, path)),
});
} else {
// TODO Temporarily tolerate this
if bus_route.route_type == PathConstraints::Train {
println!(
"No route between bus stops {:?} and {:?}",
stop1_id, bus_route.stops[stop2_idx]
);
return Vec::new();
} else {
panic!(
"No route between bus stops {:?} and {:?}",
stop1_id, bus_route.stops[stop2_idx]
);
}
panic!("No route between stops: {}", req);
}
}
let results = stops
.iter()
.map(|s| {
(
s.next_stop_idx,
s.req.clone(),
s.path_to_next_stop.clone(),
stops[s.next_stop_idx].driving_pos.dist_along(),
)
})
.collect();
let first_step = stops[0].next_stop.clone().unwrap();
self.routes.insert(
bus_route.id,
Route {
buses: Vec::new(),
buses: BTreeSet::new(),
stops,
},
);
results
first_step
}
pub fn bus_created(&mut self, bus: CarID, route: BusRouteID, next_stop_idx: StopIdx) {
self.routes.get_mut(&route).unwrap().buses.push(bus);
pub fn bus_created(&mut self, bus: CarID, route: BusRouteID) {
self.routes.get_mut(&route).unwrap().buses.insert(bus);
self.buses.insert(
bus,
Bus {
car: bus,
route,
passengers: Vec::new(),
state: BusState::DrivingToStop(next_stop_idx),
state: BusState::DrivingToStop(1),
},
);
}
@ -216,24 +190,26 @@ impl TransitSimState {
self.peds_waiting.insert(stop1, still_waiting);
}
BusState::AtStop(_) => unreachable!(),
};
}
}
pub fn bus_departed_from_stop(&mut self, id: CarID) -> Router {
pub fn bus_departed_from_stop(&mut self, id: CarID, map: &Map) -> Router {
let mut bus = self.buses.get_mut(&id).unwrap();
match bus.state {
BusState::DrivingToStop(_) => unreachable!(),
BusState::AtStop(stop_idx) => {
let route = &self.routes[&bus.route];
let stop = &route.stops[stop_idx];
bus.state = BusState::DrivingToStop(stop.next_stop_idx);
let stop = &self.routes[&bus.route].stops[stop_idx];
self.events
.push(Event::BusDepartedFromStop(id, bus.route, stop.id));
Router::follow_bus_route(
stop.path_to_next_stop.clone(),
route.stops[stop.next_stop_idx].driving_pos.dist_along(),
)
if let Some((req, path)) = stop.next_stop.clone() {
bus.state = BusState::DrivingToStop(stop_idx + 1);
Router::follow_bus_route(path.clone(), req.end.dist_along())
} else {
let on = stop.driving_pos.lane();
self.routes.get_mut(&bus.route).unwrap().buses.remove(&id);
self.buses.remove(&id);
Router::vanish_bus(on, map)
}
}
}
}
@ -300,20 +276,14 @@ impl TransitSimState {
self.buses[&bus].route
}
// also stop idx
// also stop idx that the bus is coming from
pub fn buses_for_route(&self, route: BusRouteID) -> Vec<(CarID, usize)> {
if let Some(ref r) = self.routes.get(&route) {
r.buses
.iter()
.map(|bus| {
let stop = match self.buses[bus].state {
BusState::DrivingToStop(idx) => {
if idx == 0 {
r.stops.len() - 1
} else {
idx - 1
}
}
BusState::DrivingToStop(idx) => idx - 1,
BusState::AtStop(idx) => idx,
};
(*bus, stop)