mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 23:15:24 +03:00
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:
parent
0ad14d17ac
commit
91a9a9a1bc
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
|
@ -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| {
|
||||
|
@ -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)");
|
||||
|
@ -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)
|
||||
}
|
@ -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> {
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
133
sim/src/sim.rs
133
sim/src/sim.rs
@ -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()),
|
||||
]
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user