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 map.bus_routes
.extend(extract_route(&tags, rel, &doc, &id_to_way, &map.gps_bounds)); .extend(extract_route(&tags, rel, &doc, &id_to_way, &map.gps_bounds));
} else if tags.is("type", "multipolygon") && tags.contains_key("amenity") { } 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( fn extract_route(
master_tags: &Tags, tags: &Tags,
master_rel: &osm_xml::Relation, rel: &osm_xml::Relation,
doc: &osm_xml::OSM, doc: &osm_xml::OSM,
id_to_way: &HashMap<i64, Vec<Pt2D>>, id_to_way: &HashMap<i64, Vec<Pt2D>>,
gps_bounds: &GPSBounds, gps_bounds: &GPSBounds,
) -> Option<RawBusRoute> { ) -> Option<RawBusRoute> {
let route_name = master_tags.get("name")?.clone(); let full_name = tags.get("name")?.clone();
let is_bus = match master_tags.get("route_master")?.as_ref() { let short_name = tags
.get("ref")
.cloned()
.unwrap_or_else(|| full_name.clone());
let is_bus = match tags.get("route")?.as_ref() {
"bus" => true, "bus" => true,
"light_rail" => false, "light_rail" => false,
x => { x => {
println!( println!(
"Skipping route {} of unknown type {}: {}", "Skipping route {} of unknown type {}: {}",
route_name, full_name,
x, x,
rel_url(master_rel.id) rel_url(rel.id)
); );
return None; return None;
} }
}; };
let mut directions = Vec::new(); // Gather stops in order. Platforms may exist or not; match them up by name.
for (_, route_member) in get_members(master_rel, doc) { let mut stops = Vec::new();
if let osm_xml::Reference::Relation(route_rel) = route_member { let mut platforms = HashMap::new();
let route_tags = tags_to_map(&route_rel.tags); for (role, member) in get_members(rel, doc) {
assert_eq!(route_tags.get("type"), Some(&"route".to_string())); if role == "stop" {
// Gather stops in order. Platforms may exist or not; match them up by name. if let osm_xml::Reference::Node(node) = member {
let mut stops = Vec::new(); stops.push(RawBusStop {
let mut platforms = HashMap::new(); name: tags_to_map(&node.tags)
for (role, member) in get_members(&route_rel, doc) { .get("name")
if role == "stop" { .cloned()
if let osm_xml::Reference::Node(node) = member { .unwrap_or_else(|| format!("stop #{}", stops.len() + 1)),
stops.push(RawBusStop { vehicle_pos: Pt2D::from_gps(LonLat::new(node.lon, node.lat), gps_bounds),
name: tags_to_map(&node.tags) ped_pos: None,
.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);
} }
} 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 directions.len() == 2 { if let Some(pt) = platforms.remove(&stop.name) {
Some(RawBusRoute { stop.ped_pos = Some(pt);
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
} }
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 // 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) { if let Some(r) = app.primary.sim.bus_route_id(c) {
osd.append_all(vec![ osd.append_all(vec![
Line(" serving "), 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 let routes: BTreeSet<String> = map
.get_routes_serving_stop(bs) .get_routes_serving_stop(bs)
.into_iter() .into_iter()
.map(|r| r.name.clone()) .map(|r| r.full_name.clone())
.collect(); .collect();
list_names(&mut osd, |l| l.fg(name_color), routes); 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) { for r in app.primary.map.get_routes_serving_stop(id) {
let buses = app.primary.sim.status_of_buses(r.id); let buses = app.primary.sim.status_of_buses(r.id);
if buses.is_empty() { 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 { } 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 details
.hyperlinks .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 let arrivals: Vec<(Time, CarID)> = all_arrivals
@ -110,7 +110,7 @@ fn bus_header(
Line(format!( Line(format!(
"{} (route {})", "{} (route {})",
id, id,
app.primary.map.get_br(route).name app.primary.map.get_br(route).full_name
)) ))
.small_heading() .small_heading()
.draw(ctx), .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); .bus_arrivals_over_time(app.primary.sim.time(), id);
let mut series = Vec::new(); let mut series = Vec::new();
for idx1 in 0..route.stops.len() { for idx1 in 0..route.stops.len() - 1 {
let idx2 = if idx1 == route.stops.len() - 1 { let idx2 = idx1 + 1;
0
} else {
idx1 + 1
};
series.push(Series { series.push(Series {
label: format!("Stop {}->{}", idx1 + 1, idx2 + 1), label: format!("Stop {}->{}", idx1 + 1, idx2 + 1),
color: app.cs.rotating_color_plot(idx1), 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() .get_analytics()
.bus_passenger_delays(app.primary.sim.time(), id) .bus_passenger_delays(app.primary.sim.time(), id)
.collect::<BTreeMap<_, _>>(); .collect::<BTreeMap<_, _>>();
// TODO I smell an off by one
for idx in 0..route.stops.len() { for idx in 0..route.stops.len() {
col.push(Widget::row(vec![ col.push(Widget::row(vec![
format!("Stop {}", idx + 1).draw_text(ctx), 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) { 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 // TODO Line it up right in the middle of the line of text. This is probably a bit
// wrong. // wrong.
let base_percent_y = if stop_idx == route.stops.len() - 1 { let base_percent_y = (stop_idx as f64) / ((route.stops.len() - 1) as f64);
0.0
} else {
(stop_idx as f64) / ((route.stops.len() - 1) as f64)
};
batch.push( batch.push(
Color::BLUE, Color::BLUE,
Circle::new( Circle::new(

View File

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

View File

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

View File

@ -1,17 +1,17 @@
mod bridges; mod bridges;
mod buildings; mod buildings;
mod bus_stops;
pub mod initial; pub mod initial;
mod remove_disconnected; mod remove_disconnected;
pub mod traffic_signals; pub mod traffic_signals;
mod transit;
pub mod turns; pub mod turns;
use crate::pathfind::Pathfinder; use crate::pathfind::Pathfinder;
use crate::raw::{OriginalIntersection, OriginalRoad, RawMap}; use crate::raw::{OriginalIntersection, OriginalRoad, RawMap};
use crate::{ use crate::{
connectivity, osm, Area, AreaID, BusRouteID, ControlStopSign, ControlTrafficSignal, connectivity, osm, Area, AreaID, ControlStopSign, ControlTrafficSignal, Intersection,
Intersection, IntersectionID, IntersectionType, Lane, LaneID, Map, MapEdits, PathConstraints, IntersectionID, IntersectionType, Lane, LaneID, Map, MapEdits, PathConstraints, Position, Road,
Position, Road, RoadID, Zone, RoadID, Zone,
}; };
use abstutil::Timer; use abstutil::Timer;
use enumset::EnumSet; use enumset::EnumSet;
@ -320,44 +320,9 @@ impl Map {
map.pathfinder = Some(Pathfinder::new_without_transit(&map, timer)); map.pathfinder = Some(Pathfinder::new_without_transit(&map, timer));
timer.stop("setup (most of) Pathfinder"); timer.stop("setup (most of) Pathfinder");
{ transit::make_stops_and_routes(&mut map, &raw.bus_routes, timer);
// Turn the two directions of each route into one loop. Need to do something better for id in map.bus_stops.keys() {
// with borders later. assert!(!map.get_routes_serving_stop(*id).is_empty());
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);
}
} }
timer.start("setup rest of Pathfinder (walking with transit)"); timer.start("setup rest of Pathfinder (walking with transit)");

View File

@ -6,6 +6,101 @@ use crate::{
use abstutil::Timer; use abstutil::Timer;
use geom::{Distance, HashablePt2D}; use geom::{Distance, HashablePt2D};
use std::collections::{BTreeMap, HashMap, HashSet}; 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 { struct Matcher {
sidewalk_pts: HashMap<HashablePt2D, Position>, sidewalk_pts: HashMap<HashablePt2D, Position>,
@ -14,13 +109,13 @@ struct Matcher {
} }
impl 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. // Match all of the points to an exact position along a lane.
let mut lookup_sidewalk_pts = HashSet::new(); let mut lookup_sidewalk_pts = HashSet::new();
let mut lookup_bus_pts = HashSet::new(); let mut lookup_bus_pts = HashSet::new();
let mut lookup_light_rail_pts = HashSet::new(); let mut lookup_light_rail_pts = HashSet::new();
for r in bus_routes { for r in routes {
for stop in &r.fwd_stops { for stop in &r.stops {
if r.is_bus { if r.is_bus {
lookup_bus_pts.insert(stop.vehicle_pos.to_hashable()); lookup_bus_pts.insert(stop.vehicle_pos.to_hashable());
} else { } 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( fn check_stops(
route: &str,
constraints: PathConstraints, constraints: PathConstraints,
stop1: BusStopID, stop1: BusStopID,
stop2: BusStopID, stop2: BusStopID,
map: &Map, map: &Map,
) -> bool { ) -> Result<(), Box<dyn Error>> {
let start = map.get_bs(stop1).driving_pos; let start = map.get_bs(stop1).driving_pos;
let end = map.get_bs(stop2).driving_pos; let end = map.get_bs(stop2).driving_pos;
if start.lane() == end.lane() && start.dist_along() > end.dist_along() { if start.lane() == end.lane() && start.dist_along() > end.dist_along() {
println!( return Err(format!(
"Route {} has two bus stops seemingly out of order somewhere on {}", "Two stops seemingly out of order somewhere on {}",
route,
map.get_parent(start.lane()).orig_id map.get_parent(start.lane()).orig_id
); )
return false; .into());
} }
map.pathfind(PathRequest { if map
start, .pathfind(PathRequest {
end, start,
constraints, end,
}) constraints,
.is_some() })
.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> { 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> { pub fn get_routes_serving_stop(&self, stop: BusStopID) -> Vec<&BusRoute> {

View File

@ -46,7 +46,8 @@ pub struct BusStop {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct BusRoute { pub struct BusRoute {
pub id: BusRouteID, pub id: BusRouteID,
pub name: String, pub full_name: String,
pub short_name: String,
pub stops: Vec<BusStopID>, pub stops: Vec<BusStopID>,
pub route_type: PathConstraints, 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. // Only used for weird serialization magic.
pub fn dummy() -> Path { pub fn dummy() -> Path {
Path { Path {
@ -596,7 +605,7 @@ impl Pathfinder {
timer.stop("prepare pathfinding for trains"); timer.stop("prepare pathfinding for trains");
timer.start("prepare pathfinding for pedestrians"); 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"); timer.stop("prepare pathfinding for pedestrians");
Pathfinder { Pathfinder {
@ -610,7 +619,12 @@ impl Pathfinder {
} }
pub fn setup_walking_with_transit(&mut self, map: &Map) { 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> { pub fn pathfind(&self, req: PathRequest, map: &Map) -> Option<Path> {
@ -883,14 +897,15 @@ impl Pathfinder {
// Can't edit anything related to trains // Can't edit anything related to trains
timer.start("apply edits to pedestrian pathfinding"); 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.stop("apply edits to pedestrian pathfinding");
timer.start("apply edits to pedestrian using transit pathfinding"); timer.start("apply edits to pedestrian using transit pathfinding");
self.walking_with_transit_graph self.walking_with_transit_graph
.as_mut() .as_mut()
.unwrap() .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"); timer.stop("apply edits to pedestrian using transit pathfinding");
} }
} }

View File

@ -36,7 +36,12 @@ impl WalkingNode {
} }
impl SidewalkPathfinder { 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(); let mut nodes = NodeMap::new();
// We're assuming that to start with, no sidewalks are closed for construction! // We're assuming that to start with, no sidewalks are closed for construction!
for l in map.all_lanes() { 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 { SidewalkPathfinder {
graph, graph,
nodes, 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 // The NodeMap is all sidewalks and bus stops -- it won't change. So we can also reuse the
// node ordering. // 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(); let node_ordering = self.graph.get_node_ordering();
self.graph = fast_paths::prepare_with_order(&input_graph, &node_ordering).unwrap(); self.graph = fast_paths::prepare_with_order(&input_graph, &node_ordering).unwrap();
} }
@ -126,6 +143,7 @@ fn make_input_graph(
nodes: &NodeMap<WalkingNode>, nodes: &NodeMap<WalkingNode>,
use_transit: bool, use_transit: bool,
bus_graph: &VehiclePathfinder, bus_graph: &VehiclePathfinder,
train_graph: &VehiclePathfinder,
) -> InputGraph { ) -> InputGraph {
let mut input_graph = InputGraph::new(); 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 // 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. // bus to drive between the stops. Optimistically assume no waiting time at a stop.
for route in map.all_bus_routes() { for route in map.all_bus_routes() {
// TODO Gotta connect the stops properly first for pair in route.stops.windows(2) {
if route.route_type == PathConstraints::Train { let (stop1, stop2) = (map.get_bs(pair[0]), map.get_bs(pair[1]));
continue; let graph = match route.route_type {
} PathConstraints::Bus => bus_graph,
for (stop1, stop2) in PathConstraints::Train => train_graph,
route _ => unreachable!(),
.stops };
.iter() if let Some((_, driving_cost)) = graph.pathfind(
.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(
&PathRequest { &PathRequest {
start: map.get_bs(*stop1).driving_pos, start: stop1.driving_pos,
end: map.get_bs(*stop2).driving_pos, end: stop2.driving_pos,
constraints: PathConstraints::Bus, constraints: route.route_type,
}, },
map, map,
) { ) {
input_graph.add_edge( input_graph.add_edge(
nodes.get(WalkingNode::RideBus(*stop1)), nodes.get(WalkingNode::RideBus(stop1.id)),
nodes.get(WalkingNode::RideBus(*stop2)), nodes.get(WalkingNode::RideBus(stop2.id)),
driving_cost, driving_cost,
); );
} else { } else {
panic!( panic!(
"No bus route from {} to {} now! Prevent this edit", "No bus route from {} to {} now for {}! Prevent this edit",
map.get_bs(*stop1).driving_pos, stop1.driving_pos, stop2.driving_pos, route.full_name,
map.get_bs(*stop2).driving_pos
); );
} }
} }

View File

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

View File

@ -78,8 +78,10 @@ impl TripPhaseType {
TripPhaseType::Walking => "walking".to_string(), TripPhaseType::Walking => "walking".to_string(),
TripPhaseType::Biking => "biking".to_string(), TripPhaseType::Biking => "biking".to_string(),
TripPhaseType::Parking => "parking".to_string(), TripPhaseType::Parking => "parking".to_string(),
TripPhaseType::WaitingForBus(r, _) => format!("waiting for bus {}", map.get_br(r).name), TripPhaseType::WaitingForBus(r, _) => {
TripPhaseType::RidingBus(r, _, _) => format!("riding bus {}", map.get_br(r).name), 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::Aborted => "trip aborted due to some bug".to_string(),
TripPhaseType::Finished => "trip finished".to_string(), TripPhaseType::Finished => "trip finished".to_string(),
TripPhaseType::DelayedStart => "delayed by previous trip taking too long".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 { if let Some(ref routes) = self.only_seed_buses {
for route in map.all_bus_routes() { 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); sim.seed_bus_route(route, map, timer);
} }
} }

View File

@ -214,7 +214,7 @@ impl Car {
CarState::Unparking(_, _, _) => CarStatus::Parked, CarState::Unparking(_, _, _) => CarStatus::Parked,
CarState::Parking(_, _, _) => CarStatus::Parked, CarState::Parking(_, _, _) => CarStatus::Parked,
// Changing color for idling buses is helpful // Changing color for idling buses is helpful
CarState::Idling(_, _) => CarStatus::Parked, CarState::IdlingAtStop(_, _) => CarStatus::Parked,
}, },
on: self.router.head(), on: self.router.head(),
partly_on, partly_on,
@ -223,8 +223,8 @@ impl Car {
{ {
Some( Some(
map.get_br(transit.bus_route(self.vehicle.id)) map.get_br(transit.bus_route(self.vehicle.id))
.name .short_name
.to_string(), .clone(),
) )
} else { } else {
None None
@ -249,7 +249,7 @@ pub enum CarState {
// Where's the front of the car while this is happening? // Where's the front of the car while this is happening?
Unparking(Distance, ParkingSpot, TimeInterval), Unparking(Distance, ParkingSpot, TimeInterval),
Parking(Distance, ParkingSpot, TimeInterval), Parking(Distance, ParkingSpot, TimeInterval),
Idling(Distance, TimeInterval), IdlingAtStop(Distance, TimeInterval),
} }
impl CarState { impl CarState {
@ -260,7 +260,7 @@ impl CarState {
CarState::WaitingToAdvance { .. } => unreachable!(), CarState::WaitingToAdvance { .. } => unreachable!(),
CarState::Unparking(_, _, ref time_int) => time_int.end, CarState::Unparking(_, _, ref time_int) => time_int.end,
CarState::Parking(_, _, 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::{ use crate::{
ActionAtEnd, AgentID, AgentProperties, CarID, Command, CreateCar, DistanceInterval, ActionAtEnd, AgentID, AgentProperties, CarID, Command, CreateCar, DistanceInterval,
DrawCarInput, Event, IntersectionSimState, ParkedCar, ParkingSimState, ParkingSpot, PersonID, DrawCarInput, Event, IntersectionSimState, ParkedCar, ParkingSimState, ParkingSpot, PersonID,
Scheduler, TimeInterval, TransitSimState, TripManager, UnzoomedAgent, Vehicle, VehicleType, Scheduler, TimeInterval, TransitSimState, TripManager, UnzoomedAgent, Vehicle, WalkingSimState,
WalkingSimState, FOLLOWING_DISTANCE, FOLLOWING_DISTANCE,
}; };
use abstutil::{deserialize_btreemap, serialize_btreemap}; use abstutil::{deserialize_btreemap, serialize_btreemap};
use geom::{Distance, Duration, PolyLine, Time}; use geom::{Distance, Duration, PolyLine, Time};
@ -171,7 +171,7 @@ impl DrivingSimState {
// //
// Crossing -> Queued or WaitingToAdvance // Crossing -> Queued or WaitingToAdvance
// Unparking -> Crossing // Unparking -> Crossing
// Idling -> Crossing // IdlingAtStop -> Crossing
// Queued -> last step handling (Parking or done) // Queued -> last step handling (Parking or done)
// WaitingToAdvance -> try to advance to the next step of the path // WaitingToAdvance -> try to advance to the next step of the path
// Parking -> done // Parking -> done
@ -284,8 +284,8 @@ impl DrivingSimState {
car.state = car.crossing_state(front, now, map); car.state = car.crossing_state(front, now, map);
scheduler.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id)); scheduler.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));
} }
CarState::Idling(dist, _) => { CarState::IdlingAtStop(dist, _) => {
car.router = transit.bus_departed_from_stop(car.vehicle.id); car.router = transit.bus_departed_from_stop(car.vehicle.id, map);
self.events self.events
.push(Event::PathAmended(car.router.get_path().clone())); .push(Event::PathAmended(car.router.get_path().clone()));
car.state = car.crossing_state(dist, now, map); car.state = car.crossing_state(dist, now, map);
@ -325,7 +325,7 @@ impl DrivingSimState {
CarState::Crossing(_, _) CarState::Crossing(_, _)
| CarState::Unparking(_, _, _) | CarState::Unparking(_, _, _)
| CarState::Parking(_, _, _) | CarState::Parking(_, _, _)
| CarState::Idling(_, _) => {} | CarState::IdlingAtStop(_, _) => {}
} }
} }
} }
@ -429,7 +429,7 @@ impl DrivingSimState {
match car.state { match car.state {
CarState::Crossing(_, _) CarState::Crossing(_, _)
| CarState::Unparking(_, _, _) | CarState::Unparking(_, _, _)
| CarState::Idling(_, _) | CarState::IdlingAtStop(_, _)
| CarState::WaitingToAdvance { .. } => unreachable!(), | CarState::WaitingToAdvance { .. } => unreachable!(),
CarState::Queued { blocked_since } => { CarState::Queued { blocked_since } => {
match car.router.maybe_handle_end( match car.router.maybe_handle_end(
@ -514,15 +514,10 @@ impl DrivingSimState {
map, map,
); );
car.total_blocked_time += now - blocked_since; car.total_blocked_time += now - blocked_since;
// TODO Light rail routes are all disconnected. For now, just sit forever car.state = CarState::IdlingAtStop(
// after making one stop. our_dist,
let wait_at_stop = if car.vehicle.vehicle_type == VehicleType::Train { TimeInterval::new(now, now + TIME_TO_WAIT_AT_STOP),
Duration::hours(24 * 30) );
} else {
TIME_TO_WAIT_AT_STOP
};
car.state =
CarState::Idling(our_dist, TimeInterval::new(now, now + wait_at_stop));
scheduler scheduler
.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id)); .push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));
true true
@ -673,7 +668,7 @@ impl DrivingSimState {
// They weren't blocked // They weren't blocked
CarState::Unparking(_, _, _) CarState::Unparking(_, _, _)
| CarState::Parking(_, _, _) | CarState::Parking(_, _, _)
| CarState::Idling(_, _) => {} | CarState::IdlingAtStop(_, _) => {}
CarState::WaitingToAdvance { .. } => unreachable!(), CarState::WaitingToAdvance { .. } => unreachable!(),
} }
} }
@ -813,7 +808,7 @@ impl DrivingSimState {
CarState::Crossing(_, _) CarState::Crossing(_, _)
| CarState::Unparking(_, _, _) | CarState::Unparking(_, _, _)
| CarState::Parking(_, _, _) | CarState::Parking(_, _, _)
| CarState::Idling(_, _) => {} | CarState::IdlingAtStop(_, _) => {}
} }
} }
} else { } else {

View File

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

View File

@ -4,8 +4,8 @@ use crate::{
}; };
use geom::Distance; use geom::Distance;
use map_model::{ use map_model::{
BuildingID, IntersectionID, Map, Path, PathConstraints, PathRequest, PathStep, Position, BuildingID, IntersectionID, LaneID, Map, Path, PathConstraints, PathRequest, PathStep,
Traversable, TurnID, TurnType, Position, Traversable, TurnID, TurnType,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -57,6 +57,16 @@ impl Router {
goal: Goal::EndAtBorder { end_dist, i }, 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 { pub fn park_near(path: Path, bldg: BuildingID) -> Router {
Router { Router {

View File

@ -13,7 +13,7 @@ use geom::{Distance, Duration, PolyLine, Pt2D, Speed, Time};
use instant::Instant; use instant::Instant;
use map_model::{ use map_model::{
BuildingID, BusRoute, BusRouteID, IntersectionID, LaneID, Map, ParkingLotID, Path, BuildingID, BusRoute, BusRouteID, IntersectionID, LaneID, Map, ParkingLotID, Path,
PathConstraints, PathRequest, PathStep, Position, RoadID, Traversable, PathConstraints, PathRequest, Position, RoadID, Traversable,
}; };
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -227,80 +227,65 @@ impl Sim {
self.parking.add_parked_car(ParkedCar { vehicle, spot }); self.parking.add_parked_car(ParkedCar { vehicle, spot });
} }
pub fn seed_bus_route(&mut self, route: &BusRoute, map: &Map, timer: &mut Timer) -> Vec<CarID> { // TODO Change this to be a periodic "start a bus at stop1 based on a schedule"
let mut results: Vec<CarID> = Vec::new(); pub(crate) fn seed_bus_route(&mut self, route: &BusRoute, map: &Map, timer: &mut Timer) {
if route.stops.is_empty() {
// Try to spawn just ONE bus anywhere. panic!("{} has no stops", route.full_name);
// 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);
}
}
} }
if results.is_empty() { // TODO This'll be valid when we have borders too
// TODO Bigger failure if route.stops.len() == 1 {
timer.warn(format!("Failed to make ANY buses for {}!", route.name)); 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) { pub fn set_name(&mut self, name: String) {
@ -938,7 +923,7 @@ impl Sim {
vec![ vec![
( (
"Route".to_string(), "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()), ("Passengers".to_string(), passengers.len().to_string()),
] ]

View File

@ -3,29 +3,25 @@ use crate::{
WalkingSimState, WalkingSimState,
}; };
use abstutil::{deserialize_btreemap, serialize_btreemap}; use abstutil::{deserialize_btreemap, serialize_btreemap};
use geom::{Distance, Time}; use geom::Time;
use map_model::{ use map_model::{BusRoute, BusRouteID, BusStopID, Map, Path, PathRequest, Position};
BusRoute, BusRouteID, BusStopID, Map, Path, PathConstraints, PathRequest, Position,
};
use serde::{Deserialize, Serialize}; 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. // These index stops along a route, not stops along a single sidewalk.
type StopIdx = usize; type StopIdx = usize;
#[derive(Serialize, Deserialize, PartialEq, Clone)] #[derive(Serialize, Deserialize, PartialEq, Clone)]
struct StopForRoute { struct Stop {
id: BusStopID, id: BusStopID,
driving_pos: Position, driving_pos: Position,
req: PathRequest, next_stop: Option<(PathRequest, Path)>,
path_to_next_stop: Path,
next_stop_idx: StopIdx,
} }
#[derive(Serialize, Deserialize, PartialEq, Clone)] #[derive(Serialize, Deserialize, PartialEq, Clone)]
struct Route { struct Route {
stops: Vec<StopForRoute>, stops: Vec<Stop>,
buses: Vec<CarID>, buses: BTreeSet<CarID>,
} }
#[derive(Serialize, Deserialize, PartialEq, Clone)] #[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 // Returns the path and end distance for stop1->stop2.
// route. pub fn create_empty_route(&mut self, bus_route: &BusRoute, map: &Map) -> (PathRequest, Path) {
pub fn create_empty_route(
&mut self,
bus_route: &BusRoute,
map: &Map,
) -> Vec<(StopIdx, PathRequest, Path, Distance)> {
assert!(bus_route.stops.len() > 1); assert!(bus_route.stops.len() > 1);
let mut stops = Vec::new(); let mut stops = Vec::new();
for (idx, stop1_id) in bus_route.stops.iter().enumerate() { for (idx, stop1_id) in bus_route.stops.iter().enumerate() {
let stop1 = map.get_bs(*stop1_id); let stop1 = map.get_bs(*stop1_id);
let stop2_idx = if idx + 1 == bus_route.stops.len() { if idx == bus_route.stops.len() - 1 {
0 stops.push(Stop {
} else { id: stop1.id,
idx + 1 driving_pos: stop1.driving_pos,
}; next_stop: None,
});
continue;
}
let req = PathRequest { let req = PathRequest {
start: stop1.driving_pos, 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, constraints: bus_route.route_type,
}; };
if let Some(path) = map.pathfind(req.clone()) { if let Some(path) = map.pathfind(req.clone()) {
stops.push(StopForRoute { if path.is_empty() {
id: *stop1_id, panic!("Empty path between stops?! {}", req);
}
stops.push(Stop {
id: stop1.id,
driving_pos: stop1.driving_pos, driving_pos: stop1.driving_pos,
req, next_stop: Some((req, path)),
path_to_next_stop: path,
next_stop_idx: stop2_idx,
}); });
} else { } else {
// TODO Temporarily tolerate this panic!("No route between stops: {}", req);
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]
);
}
} }
} }
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( self.routes.insert(
bus_route.id, bus_route.id,
Route { Route {
buses: Vec::new(), buses: BTreeSet::new(),
stops, stops,
}, },
); );
results first_step
} }
pub fn bus_created(&mut self, bus: CarID, route: BusRouteID, next_stop_idx: StopIdx) { pub fn bus_created(&mut self, bus: CarID, route: BusRouteID) {
self.routes.get_mut(&route).unwrap().buses.push(bus); self.routes.get_mut(&route).unwrap().buses.insert(bus);
self.buses.insert( self.buses.insert(
bus, bus,
Bus { Bus {
car: bus, car: bus,
route, route,
passengers: Vec::new(), 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); self.peds_waiting.insert(stop1, still_waiting);
} }
BusState::AtStop(_) => unreachable!(), 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(); let mut bus = self.buses.get_mut(&id).unwrap();
match bus.state { match bus.state {
BusState::DrivingToStop(_) => unreachable!(), BusState::DrivingToStop(_) => unreachable!(),
BusState::AtStop(stop_idx) => { BusState::AtStop(stop_idx) => {
let route = &self.routes[&bus.route]; let stop = &self.routes[&bus.route].stops[stop_idx];
let stop = &route.stops[stop_idx];
bus.state = BusState::DrivingToStop(stop.next_stop_idx);
self.events self.events
.push(Event::BusDepartedFromStop(id, bus.route, stop.id)); .push(Event::BusDepartedFromStop(id, bus.route, stop.id));
Router::follow_bus_route( if let Some((req, path)) = stop.next_stop.clone() {
stop.path_to_next_stop.clone(), bus.state = BusState::DrivingToStop(stop_idx + 1);
route.stops[stop.next_stop_idx].driving_pos.dist_along(), 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 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)> { pub fn buses_for_route(&self, route: BusRouteID) -> Vec<(CarID, usize)> {
if let Some(ref r) = self.routes.get(&route) { if let Some(ref r) = self.routes.get(&route) {
r.buses r.buses
.iter() .iter()
.map(|bus| { .map(|bus| {
let stop = match self.buses[bus].state { let stop = match self.buses[bus].state {
BusState::DrivingToStop(idx) => { BusState::DrivingToStop(idx) => idx - 1,
if idx == 0 {
r.stops.len() - 1
} else {
idx - 1
}
}
BusState::AtStop(idx) => idx, BusState::AtStop(idx) => idx,
}; };
(*bus, stop) (*bus, stop)