mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-27 15:03:20 +03:00
Split raw_map into a new street_network crate.
MapConfig has a new bool, so we will need to regenerate all data.
This commit is contained in:
parent
c95c16454b
commit
9980cf0a35
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -3155,17 +3155,11 @@ dependencies = [
|
||||
name = "raw_map"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aabb-quadtree",
|
||||
"abstio",
|
||||
"abstutil",
|
||||
"anyhow",
|
||||
"geojson",
|
||||
"geom",
|
||||
"kml",
|
||||
"log",
|
||||
"petgraph",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"street_network",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
@ -3711,6 +3705,23 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
|
||||
|
||||
[[package]]
|
||||
name = "street_network"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aabb-quadtree",
|
||||
"abstio",
|
||||
"abstutil",
|
||||
"anyhow",
|
||||
"geojson",
|
||||
"geom",
|
||||
"kml",
|
||||
"log",
|
||||
"petgraph",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -17,6 +17,7 @@ members = [
|
||||
"popdat",
|
||||
"raw_map",
|
||||
"sim",
|
||||
"street_network",
|
||||
"synthpop",
|
||||
"tests",
|
||||
"traffic_seitan",
|
||||
|
@ -54,7 +54,7 @@ impl MainState {
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
let bounds = app.model.map.gps_bounds.to_bounds();
|
||||
let bounds = app.model.map.streets.gps_bounds.to_bounds();
|
||||
ctx.canvas.map_dims = (bounds.width(), bounds.height());
|
||||
|
||||
let mut state = MainState {
|
||||
@ -263,14 +263,17 @@ impl State<App> for MainState {
|
||||
self.mode = Mode::SetBoundaryPt1;
|
||||
}
|
||||
"detect short roads" => {
|
||||
for r in app.model.map.find_dog_legs() {
|
||||
for r in app.model.map.streets.find_dog_legs() {
|
||||
app.model.road_deleted(r);
|
||||
app.model.road_added(ctx, r);
|
||||
}
|
||||
}
|
||||
"simplify RawMap" => {
|
||||
ctx.loading_screen("simplify", |ctx, timer| {
|
||||
app.model.map.run_all_simplifications(false, true, timer);
|
||||
app.model
|
||||
.map
|
||||
.streets
|
||||
.run_all_simplifications(false, true, timer);
|
||||
app.model.recreate_world(ctx, timer);
|
||||
});
|
||||
}
|
||||
@ -306,7 +309,7 @@ impl State<App> for MainState {
|
||||
}
|
||||
Mode::CreatingRoad(i1) => {
|
||||
if ctx.canvas_movement() {
|
||||
URLManager::update_url_cam(ctx, &app.model.map.gps_bounds);
|
||||
URLManager::update_url_cam(ctx, &app.model.map.streets.gps_bounds);
|
||||
}
|
||||
|
||||
if ctx.input.pressed(Key::Escape) {
|
||||
@ -322,7 +325,7 @@ impl State<App> for MainState {
|
||||
}
|
||||
Mode::SetBoundaryPt1 => {
|
||||
if ctx.canvas_movement() {
|
||||
URLManager::update_url_cam(ctx, &app.model.map.gps_bounds);
|
||||
URLManager::update_url_cam(ctx, &app.model.map.streets.gps_bounds);
|
||||
}
|
||||
|
||||
let mut txt = Text::new();
|
||||
@ -341,7 +344,7 @@ impl State<App> for MainState {
|
||||
}
|
||||
Mode::SetBoundaryPt2(pt1) => {
|
||||
if ctx.canvas_movement() {
|
||||
URLManager::update_url_cam(ctx, &app.model.map.gps_bounds);
|
||||
URLManager::update_url_cam(ctx, &app.model.map.streets.gps_bounds);
|
||||
}
|
||||
|
||||
let mut txt = Text::new();
|
||||
@ -371,7 +374,7 @@ impl State<App> for MainState {
|
||||
|
||||
g.draw_polygon(
|
||||
Color::rgb(242, 239, 233),
|
||||
app.model.map.boundary_polygon.clone(),
|
||||
app.model.map.streets.boundary_polygon.clone(),
|
||||
);
|
||||
app.model.world.draw(g);
|
||||
g.redraw(&app.model.draw_extra);
|
||||
@ -380,7 +383,8 @@ impl State<App> for MainState {
|
||||
Mode::Neutral | Mode::SetBoundaryPt1 => {}
|
||||
Mode::CreatingRoad(i1) => {
|
||||
if let Some(cursor) = g.get_cursor_in_map_space() {
|
||||
if let Ok(l) = Line::new(app.model.map.intersections[&i1].point, cursor) {
|
||||
if let Ok(l) = Line::new(app.model.map.streets.intersections[&i1].point, cursor)
|
||||
{
|
||||
g.draw_polygon(Color::GREEN, l.make_polygons(Distance::meters(5.0)));
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ pub struct EditRoad {
|
||||
|
||||
impl EditRoad {
|
||||
pub(crate) fn new_state(ctx: &mut EventCtx, app: &App, r: OriginalRoad) -> Box<dyn State<App>> {
|
||||
let road = &app.model.map.roads[&r];
|
||||
let road = &app.model.map.streets.roads[&r];
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
if let Ok(pl) = app.model.map.trimmed_road_geometry(r) {
|
||||
if let Ok(pl) = app.model.map.streets.trimmed_road_geometry(r) {
|
||||
batch.push(
|
||||
Color::BLACK,
|
||||
pl.make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
|
||||
@ -32,7 +32,7 @@ impl EditRoad {
|
||||
"Length before trimming: {}",
|
||||
road.untrimmed_road_geometry().0.length()
|
||||
)));
|
||||
if let Ok(pl) = app.model.map.trimmed_road_geometry(r) {
|
||||
if let Ok(pl) = app.model.map.streets.trimmed_road_geometry(r) {
|
||||
txt.add_line(Line(format!("Length after trimming: {}", pl.length())));
|
||||
}
|
||||
for (rt, to) in &road.turn_restrictions {
|
||||
@ -160,7 +160,7 @@ impl SimpleState<App> for EditRoad {
|
||||
"Apply" => {
|
||||
app.model.road_deleted(self.r);
|
||||
|
||||
let road = app.model.map.roads.get_mut(&self.r).unwrap();
|
||||
let road = app.model.map.streets.roads.get_mut(&self.r).unwrap();
|
||||
|
||||
road.osm_tags.remove("lanes");
|
||||
road.osm_tags.remove("oneway");
|
||||
@ -200,7 +200,7 @@ impl SimpleState<App> for EditRoad {
|
||||
}
|
||||
|
||||
road.lane_specs_ltr =
|
||||
raw_map::get_lane_specs_ltr(&road.osm_tags, &app.model.map.config);
|
||||
raw_map::get_lane_specs_ltr(&road.osm_tags, &app.model.map.streets.config);
|
||||
|
||||
app.model.road_added(ctx, self.r);
|
||||
Transition::Pop
|
||||
|
@ -81,7 +81,7 @@ pub fn load_map(
|
||||
if !URLManager::change_camera(
|
||||
ctx,
|
||||
center_camera.as_ref(),
|
||||
&app.model.map.gps_bounds,
|
||||
&app.model.map.streets.gps_bounds,
|
||||
) && !app.model.map.name.map.is_empty()
|
||||
{
|
||||
CameraState::load(ctx, &app.model.map.name);
|
||||
|
@ -72,14 +72,21 @@ impl Model {
|
||||
}
|
||||
timer.start_iter(
|
||||
"fill out world with intersections",
|
||||
self.map.intersections.len(),
|
||||
self.map.streets.intersections.len(),
|
||||
);
|
||||
for id in self.map.intersections.keys().cloned().collect::<Vec<_>>() {
|
||||
for id in self
|
||||
.map
|
||||
.streets
|
||||
.intersections
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
timer.next();
|
||||
self.intersection_added(ctx, id);
|
||||
}
|
||||
timer.start_iter("fill out world with roads", self.map.roads.len());
|
||||
for id in self.map.roads.keys().cloned().collect::<Vec<_>>() {
|
||||
timer.start_iter("fill out world with roads", self.map.streets.roads.len());
|
||||
for id in self.map.streets.roads.keys().cloned().collect::<Vec<_>>() {
|
||||
timer.next();
|
||||
self.road_added(ctx, id);
|
||||
}
|
||||
@ -100,10 +107,10 @@ impl Model {
|
||||
for b in self.map.buildings.values_mut() {
|
||||
b.polygon = b.polygon.translate(-top_left.x(), -top_left.y());
|
||||
}
|
||||
for i in self.map.intersections.values_mut() {
|
||||
for i in self.map.streets.intersections.values_mut() {
|
||||
i.point = i.point.offset(-top_left.x(), -top_left.y());
|
||||
}
|
||||
for r in self.map.roads.values_mut() {
|
||||
for r in self.map.streets.roads.values_mut() {
|
||||
for pt in &mut r.osm_center_points {
|
||||
*pt = pt.offset(-top_left.x(), -top_left.y());
|
||||
}
|
||||
@ -111,16 +118,22 @@ impl Model {
|
||||
let pt1 = Pt2D::new(0.0, 0.0);
|
||||
let pt2 = bottom_right.offset(-top_left.x(), -top_left.y());
|
||||
|
||||
self.map.boundary_polygon = Polygon::rectangle_two_corners(pt1, pt2).unwrap();
|
||||
self.map.streets.boundary_polygon = Polygon::rectangle_two_corners(pt1, pt2).unwrap();
|
||||
|
||||
// Make gps_bounds sane
|
||||
let mut seattle_bounds = GPSBounds::new();
|
||||
seattle_bounds.update(LonLat::new(-122.453224, 47.723277));
|
||||
seattle_bounds.update(LonLat::new(-122.240505, 47.495342));
|
||||
|
||||
self.map.gps_bounds = GPSBounds::new();
|
||||
self.map.gps_bounds.update(pt1.to_gps(&seattle_bounds));
|
||||
self.map.gps_bounds.update(pt2.to_gps(&seattle_bounds));
|
||||
self.map.streets.gps_bounds = GPSBounds::new();
|
||||
self.map
|
||||
.streets
|
||||
.gps_bounds
|
||||
.update(pt1.to_gps(&seattle_bounds));
|
||||
self.map
|
||||
.streets
|
||||
.gps_bounds
|
||||
.update(pt2.to_gps(&seattle_bounds));
|
||||
|
||||
self.recreate_world(ctx, &mut Timer::throwaway());
|
||||
}
|
||||
@ -132,10 +145,10 @@ impl Model {
|
||||
bounds.update(*pt);
|
||||
}
|
||||
}
|
||||
for i in self.map.intersections.values() {
|
||||
for i in self.map.streets.intersections.values() {
|
||||
bounds.update(i.point);
|
||||
}
|
||||
for r in self.map.roads.values() {
|
||||
for r in self.map.streets.roads.values() {
|
||||
for pt in &r.osm_center_points {
|
||||
bounds.update(*pt);
|
||||
}
|
||||
@ -147,7 +160,7 @@ impl Model {
|
||||
// Intersections
|
||||
impl Model {
|
||||
fn intersection_added(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
||||
let i = &self.map.intersections[&id];
|
||||
let i = &self.map.streets.intersections[&id];
|
||||
let color = match i.intersection_type {
|
||||
IntersectionType::TrafficSignal => Color::GREEN,
|
||||
IntersectionType::StopSign => Color::RED,
|
||||
@ -155,17 +168,18 @@ impl Model {
|
||||
IntersectionType::Construction => Color::ORANGE,
|
||||
};
|
||||
|
||||
let poly = if self.intersection_geom && !self.map.roads_per_intersection(id).is_empty() {
|
||||
match self.map.preview_intersection(id) {
|
||||
Ok((poly, _, _)) => poly,
|
||||
Err(err) => {
|
||||
error!("No geometry for {}: {}", id, err);
|
||||
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
||||
let poly =
|
||||
if self.intersection_geom && !self.map.streets.roads_per_intersection(id).is_empty() {
|
||||
match self.map.streets.preview_intersection(id) {
|
||||
Ok((poly, _, _)) => poly,
|
||||
Err(err) => {
|
||||
error!("No geometry for {}: {}", id, err);
|
||||
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
||||
};
|
||||
} else {
|
||||
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
||||
};
|
||||
|
||||
self.world
|
||||
.add(ID::Intersection(id))
|
||||
@ -183,8 +197,9 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn create_i(&mut self, ctx: &EventCtx, point: Pt2D) {
|
||||
let id = self.map.new_osm_node_id(time_to_id());
|
||||
let id = self.map.streets.new_osm_node_id(time_to_id());
|
||||
self.map
|
||||
.streets
|
||||
.intersections
|
||||
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
|
||||
self.intersection_added(ctx, id);
|
||||
@ -192,7 +207,7 @@ impl Model {
|
||||
|
||||
pub fn move_i(&mut self, ctx: &EventCtx, id: osm::NodeID, point: Pt2D) {
|
||||
self.world.delete_before_replacement(ID::Intersection(id));
|
||||
for r in self.map.move_intersection(id, point).unwrap() {
|
||||
for r in self.map.streets.move_intersection(id, point).unwrap() {
|
||||
self.road_deleted(r);
|
||||
self.road_added(ctx, r);
|
||||
}
|
||||
@ -200,18 +215,18 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn delete_i(&mut self, id: osm::NodeID) {
|
||||
if !self.map.can_delete_intersection(id) {
|
||||
if !self.map.streets.can_delete_intersection(id) {
|
||||
error!("Can't delete intersection used by roads");
|
||||
return;
|
||||
}
|
||||
self.map.delete_intersection(id);
|
||||
self.map.streets.delete_intersection(id);
|
||||
self.world.delete(ID::Intersection(id));
|
||||
}
|
||||
|
||||
pub fn toggle_i(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
||||
self.world.delete_before_replacement(ID::Intersection(id));
|
||||
|
||||
let i = self.map.intersections.get_mut(&id).unwrap();
|
||||
let i = self.map.streets.intersections.get_mut(&id).unwrap();
|
||||
if i.intersection_type == IntersectionType::TrafficSignal {
|
||||
i.intersection_type = IntersectionType::StopSign;
|
||||
} else if i.intersection_type == IntersectionType::StopSign {
|
||||
@ -225,8 +240,18 @@ impl Model {
|
||||
self.intersection_geom = show;
|
||||
|
||||
ctx.loading_screen("show intersection geometry", |ctx, timer| {
|
||||
timer.start_iter("intersection geometry", self.map.intersections.len());
|
||||
for id in self.map.intersections.keys().cloned().collect::<Vec<_>>() {
|
||||
timer.start_iter(
|
||||
"intersection geometry",
|
||||
self.map.streets.intersections.len(),
|
||||
);
|
||||
for id in self
|
||||
.map
|
||||
.streets
|
||||
.intersections
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
timer.next();
|
||||
self.world.delete_before_replacement(ID::Intersection(id));
|
||||
self.intersection_added(ctx, id);
|
||||
@ -239,7 +264,7 @@ impl Model {
|
||||
|
||||
pub fn debug_intersection_geometry(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
||||
let mut batch = GeomBatch::new();
|
||||
match self.map.preview_intersection(id) {
|
||||
match self.map.streets.preview_intersection(id) {
|
||||
Ok((_, _, labels)) => {
|
||||
for (label, polygon) in labels {
|
||||
let txt_batch = Text::from(Line(label).fg(Color::CYAN))
|
||||
@ -261,7 +286,7 @@ impl Model {
|
||||
// Roads
|
||||
impl Model {
|
||||
pub fn road_added(&mut self, ctx: &EventCtx, id: OriginalRoad) {
|
||||
let road = &self.map.roads[&id];
|
||||
let road = &self.map.streets.roads[&id];
|
||||
let (center, total_width) = road.untrimmed_road_geometry();
|
||||
let hitbox = center.make_polygons(total_width);
|
||||
let mut draw = GeomBatch::new();
|
||||
@ -301,6 +326,7 @@ impl Model {
|
||||
// Ban cul-de-sacs, since they get stripped out later anyway.
|
||||
if self
|
||||
.map
|
||||
.streets
|
||||
.roads
|
||||
.keys()
|
||||
.any(|r| (r.i1 == i1 && r.i2 == i2) || (r.i1 == i2 && r.i2 == i1))
|
||||
@ -328,11 +354,11 @@ impl Model {
|
||||
|
||||
let road = match RawRoad::new(
|
||||
vec![
|
||||
self.map.intersections[&i1].point,
|
||||
self.map.intersections[&i2].point,
|
||||
self.map.streets.intersections[&i1].point,
|
||||
self.map.streets.intersections[&i2].point,
|
||||
],
|
||||
osm_tags,
|
||||
&self.map.config,
|
||||
&self.map.streets.config,
|
||||
) {
|
||||
Ok(road) => road,
|
||||
Err(err) => {
|
||||
@ -344,7 +370,7 @@ impl Model {
|
||||
self.world.delete_before_replacement(ID::Intersection(i1));
|
||||
self.world.delete_before_replacement(ID::Intersection(i2));
|
||||
|
||||
self.map.roads.insert(id, road);
|
||||
self.map.streets.roads.insert(id, road);
|
||||
self.road_added(ctx, id);
|
||||
|
||||
self.intersection_added(ctx, i1);
|
||||
@ -358,7 +384,7 @@ impl Model {
|
||||
.delete_before_replacement(ID::Intersection(id.i1));
|
||||
self.world
|
||||
.delete_before_replacement(ID::Intersection(id.i2));
|
||||
self.map.roads.remove(&id).unwrap();
|
||||
self.map.streets.roads.remove(&id).unwrap();
|
||||
|
||||
self.intersection_added(ctx, id.i1);
|
||||
self.intersection_added(ctx, id.i2);
|
||||
@ -373,7 +399,7 @@ impl Model {
|
||||
}
|
||||
self.showing_pts = Some(id);
|
||||
|
||||
let r = &self.map.roads[&id];
|
||||
let r = &self.map.streets.roads[&id];
|
||||
for (idx, pt) in r.osm_center_points.iter().enumerate() {
|
||||
// Don't show handles for the intersections
|
||||
if idx != 0 && idx != r.osm_center_points.len() - 1 {
|
||||
@ -395,7 +421,7 @@ impl Model {
|
||||
return;
|
||||
}
|
||||
self.showing_pts = None;
|
||||
for idx in 1..=self.map.roads[&id].osm_center_points.len() - 2 {
|
||||
for idx in 1..=self.map.streets.roads[&id].osm_center_points.len() - 2 {
|
||||
self.world.delete(ID::RoadPoint(id, idx));
|
||||
}
|
||||
}
|
||||
@ -404,7 +430,7 @@ impl Model {
|
||||
assert_eq!(self.showing_pts, Some(id));
|
||||
// stop_showing_pts deletes the points, but we want to use delete_before_replacement
|
||||
self.showing_pts = None;
|
||||
for idx in 1..=self.map.roads[&id].osm_center_points.len() - 2 {
|
||||
for idx in 1..=self.map.streets.roads[&id].osm_center_points.len() - 2 {
|
||||
self.world.delete_before_replacement(ID::RoadPoint(id, idx));
|
||||
}
|
||||
|
||||
@ -414,7 +440,13 @@ impl Model {
|
||||
self.world
|
||||
.delete_before_replacement(ID::Intersection(id.i2));
|
||||
|
||||
let pts = &mut self.map.roads.get_mut(&id).unwrap().osm_center_points;
|
||||
let pts = &mut self
|
||||
.map
|
||||
.streets
|
||||
.roads
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.osm_center_points;
|
||||
pts[idx] = point;
|
||||
|
||||
self.road_added(ctx, id);
|
||||
@ -438,7 +470,13 @@ impl Model {
|
||||
self.world
|
||||
.delete_before_replacement(ID::Intersection(id.i2));
|
||||
|
||||
let pts = &mut self.map.roads.get_mut(&id).unwrap().osm_center_points;
|
||||
let pts = &mut self
|
||||
.map
|
||||
.streets
|
||||
.roads
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.osm_center_points;
|
||||
transform(pts);
|
||||
|
||||
self.road_added(ctx, id);
|
||||
@ -477,7 +515,7 @@ impl Model {
|
||||
self.stop_showing_pts(id);
|
||||
|
||||
let (retained_i, deleted_i, deleted_roads, created_roads) =
|
||||
match self.map.merge_short_road(id) {
|
||||
match self.map.streets.merge_short_road(id) {
|
||||
Ok((retained_i, deleted_i, deleted_roads, created_roads)) => {
|
||||
(retained_i, deleted_i, deleted_roads, created_roads)
|
||||
}
|
||||
@ -507,7 +545,7 @@ impl Model {
|
||||
pub fn toggle_junction(&mut self, ctx: &EventCtx, id: OriginalRoad) {
|
||||
self.road_deleted(id);
|
||||
|
||||
let road = self.map.roads.get_mut(&id).unwrap();
|
||||
let road = self.map.streets.roads.get_mut(&id).unwrap();
|
||||
if road.osm_tags.is("junction", "intersection") {
|
||||
road.osm_tags.remove("junction");
|
||||
} else {
|
||||
@ -592,14 +630,14 @@ fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
|
||||
f,
|
||||
r#"<!-- If you couldn't tell, this is a fake .osm file not representing the real world. -->"#
|
||||
)?;
|
||||
let b = &map.gps_bounds;
|
||||
let b = &map.streets.gps_bounds;
|
||||
writeln!(
|
||||
f,
|
||||
r#" <bounds minlon="{}" maxlon="{}" minlat="{}" maxlat="{}"/>"#,
|
||||
b.min_lon, b.max_lon, b.min_lat, b.max_lat
|
||||
)?;
|
||||
let mut pt_to_id: HashMap<HashablePt2D, osm::NodeID> = HashMap::new();
|
||||
for (id, i) in &map.intersections {
|
||||
for (id, i) in &map.streets.intersections {
|
||||
pt_to_id.insert(i.point.to_hashable(), *id);
|
||||
let pt = i.point.to_gps(b);
|
||||
writeln!(
|
||||
@ -610,7 +648,7 @@ fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
|
||||
pt.y()
|
||||
)?;
|
||||
}
|
||||
for (id, r) in &map.roads {
|
||||
for (id, r) in &map.streets.roads {
|
||||
writeln!(f, r#" <way id="{}">"#, id.osm_way_id.0)?;
|
||||
for pt in &r.osm_center_points {
|
||||
// TODO Make new IDs if needed
|
||||
|
@ -9,12 +9,12 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
timer.start("clipping map to boundary");
|
||||
|
||||
// So we can use retain without borrowing issues
|
||||
let boundary_polygon = map.boundary_polygon.clone();
|
||||
let boundary_polygon = map.streets.boundary_polygon.clone();
|
||||
let boundary_ring = Ring::must_new(boundary_polygon.points().clone());
|
||||
|
||||
// This is kind of indirect and slow, but first pass -- just remove roads that start or end
|
||||
// outside the boundary polygon.
|
||||
map.roads.retain(|_, r| {
|
||||
map.streets.roads.retain(|_, r| {
|
||||
let first_in = boundary_polygon.contains_pt(r.osm_center_points[0]);
|
||||
let last_in = boundary_polygon.contains_pt(*r.osm_center_points.last().unwrap());
|
||||
let light_rail_ok = if r.is_light_rail() {
|
||||
@ -33,10 +33,14 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
let mut extra_borders: BTreeMap<osm::NodeID, osm::NodeID> = BTreeMap::new();
|
||||
|
||||
// First pass: Clip roads beginning out of bounds
|
||||
let road_ids: Vec<OriginalRoad> = map.roads.keys().cloned().collect();
|
||||
let road_ids: Vec<OriginalRoad> = map.streets.roads.keys().cloned().collect();
|
||||
for id in road_ids {
|
||||
let r = &map.roads[&id];
|
||||
if map.boundary_polygon.contains_pt(r.osm_center_points[0]) {
|
||||
let r = &map.streets.roads[&id];
|
||||
if map
|
||||
.streets
|
||||
.boundary_polygon
|
||||
.contains_pt(r.osm_center_points[0])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -48,25 +52,26 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
// disconnects two roads in the map that would be connected if we left in some
|
||||
// partly-out-of-bounds road.
|
||||
if map
|
||||
.streets
|
||||
.roads
|
||||
.keys()
|
||||
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
let copy = map.intersections[&move_i].clone();
|
||||
let copy = map.streets.intersections[&move_i].clone();
|
||||
// Don't conflict with OSM IDs
|
||||
move_i = map.new_osm_node_id(-1);
|
||||
move_i = map.streets.new_osm_node_id(-1);
|
||||
extra_borders.insert(orig_id, move_i);
|
||||
map.intersections.insert(move_i, copy);
|
||||
map.streets.intersections.insert(move_i, copy);
|
||||
println!("Disconnecting {} from some other stuff (starting OOB)", id);
|
||||
}
|
||||
|
||||
let i = map.intersections.get_mut(&move_i).unwrap();
|
||||
let i = map.streets.intersections.get_mut(&move_i).unwrap();
|
||||
i.intersection_type = IntersectionType::Border;
|
||||
|
||||
// Now trim it.
|
||||
let mut mut_r = map.roads.remove(&id).unwrap();
|
||||
let mut mut_r = map.streets.roads.remove(&id).unwrap();
|
||||
let center = PolyLine::must_new(mut_r.osm_center_points.clone());
|
||||
let border_pt = boundary_ring.all_intersections(¢er)[0];
|
||||
if let Some(pl) = center.reversed().get_slice_ending_at(border_pt) {
|
||||
@ -75,7 +80,7 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
panic!("{} interacts with border strangely", id);
|
||||
}
|
||||
i.point = mut_r.osm_center_points[0];
|
||||
map.roads.insert(
|
||||
map.streets.roads.insert(
|
||||
OriginalRoad {
|
||||
osm_way_id: id.osm_way_id,
|
||||
i1: move_i,
|
||||
@ -86,10 +91,11 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
}
|
||||
|
||||
// Second pass: clip roads ending out of bounds
|
||||
let road_ids: Vec<OriginalRoad> = map.roads.keys().cloned().collect();
|
||||
let road_ids: Vec<OriginalRoad> = map.streets.roads.keys().cloned().collect();
|
||||
for id in road_ids {
|
||||
let r = &map.roads[&id];
|
||||
let r = &map.streets.roads[&id];
|
||||
if map
|
||||
.streets
|
||||
.boundary_polygon
|
||||
.contains_pt(*r.osm_center_points.last().unwrap())
|
||||
{
|
||||
@ -104,24 +110,25 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
// disconnects two roads in the map that would be connected if we left in some
|
||||
// partly-out-of-bounds road.
|
||||
if map
|
||||
.streets
|
||||
.roads
|
||||
.keys()
|
||||
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
let copy = map.intersections[&move_i].clone();
|
||||
move_i = map.new_osm_node_id(-1);
|
||||
let copy = map.streets.intersections[&move_i].clone();
|
||||
move_i = map.streets.new_osm_node_id(-1);
|
||||
extra_borders.insert(orig_id, move_i);
|
||||
map.intersections.insert(move_i, copy);
|
||||
map.streets.intersections.insert(move_i, copy);
|
||||
println!("Disconnecting {} from some other stuff (ending OOB)", id);
|
||||
}
|
||||
|
||||
let i = map.intersections.get_mut(&move_i).unwrap();
|
||||
let i = map.streets.intersections.get_mut(&move_i).unwrap();
|
||||
i.intersection_type = IntersectionType::Border;
|
||||
|
||||
// Now trim it.
|
||||
let mut mut_r = map.roads.remove(&id).unwrap();
|
||||
let mut mut_r = map.streets.roads.remove(&id).unwrap();
|
||||
let center = PolyLine::must_new(mut_r.osm_center_points.clone());
|
||||
let border_pt = boundary_ring.all_intersections(¢er.reversed())[0];
|
||||
if let Some(pl) = center.get_slice_ending_at(border_pt) {
|
||||
@ -130,7 +137,7 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
panic!("{} interacts with border strangely", id);
|
||||
}
|
||||
i.point = *mut_r.osm_center_points.last().unwrap();
|
||||
map.roads.insert(
|
||||
map.streets.roads.insert(
|
||||
OriginalRoad {
|
||||
osm_way_id: id.osm_way_id,
|
||||
i1: id.i1,
|
||||
@ -149,7 +156,11 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
|
||||
let mut result_areas = Vec::new();
|
||||
for orig_area in map.areas.drain(..) {
|
||||
for polygon in map.boundary_polygon.intersection(&orig_area.polygon) {
|
||||
for polygon in map
|
||||
.streets
|
||||
.boundary_polygon
|
||||
.intersection(&orig_area.polygon)
|
||||
{
|
||||
let mut area = orig_area.clone();
|
||||
area.polygon = polygon;
|
||||
result_areas.push(area);
|
||||
@ -160,7 +171,7 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
||||
// TODO Don't touch parking lots. It'll be visually obvious if a clip intersects one of these.
|
||||
// The boundary should be manually adjusted.
|
||||
|
||||
if map.roads.is_empty() {
|
||||
if map.streets.roads.is_empty() {
|
||||
panic!("There are no roads inside the clipping polygon");
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ fn generate_input(input: &str, map: &RawMap) -> Result<Vec<OriginalRoad>> {
|
||||
fs_err::create_dir_all(input)?;
|
||||
let mut f = BufWriter::new(File::create(format!("{input}/query"))?);
|
||||
let mut ids = Vec::new();
|
||||
for (id, r) in &map.roads {
|
||||
for (id, r) in &map.streets.roads {
|
||||
// TODO Handle cul-de-sacs
|
||||
if let Ok(pl) = PolyLine::new(r.osm_center_points.clone()) {
|
||||
ids.push(*id);
|
||||
@ -84,7 +84,13 @@ fn generate_input(input: &str, map: &RawMap) -> Result<Vec<OriginalRoad>> {
|
||||
if *pts.last().unwrap() != pl.last_pt() {
|
||||
pts.push(pl.last_pt());
|
||||
}
|
||||
for (idx, gps) in map.gps_bounds.convert_back(&pts).into_iter().enumerate() {
|
||||
for (idx, gps) in map
|
||||
.streets
|
||||
.gps_bounds
|
||||
.convert_back(&pts)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
write!(f, "{},{}", gps.x(), gps.y())?;
|
||||
if idx != pts.len() - 1 {
|
||||
write!(f, " ")?;
|
||||
@ -127,8 +133,8 @@ fn scrape_output(output: &str, map: &mut RawMap, ids: Vec<OriginalRoad>) -> Resu
|
||||
continue;
|
||||
}
|
||||
// TODO Also put total_climb and total_descent on the roads
|
||||
map.intersections.get_mut(&id.i1).unwrap().elevation = values[0];
|
||||
map.intersections.get_mut(&id.i2).unwrap().elevation = values[1];
|
||||
map.streets.intersections.get_mut(&id.i1).unwrap().elevation = values[0];
|
||||
map.streets.intersections.get_mut(&id.i2).unwrap().elevation = values[1];
|
||||
}
|
||||
if cnt != num_ids {
|
||||
bail!("Output had {} lines, but we made {} queries", cnt, num_ids);
|
||||
@ -136,8 +142,9 @@ fn scrape_output(output: &str, map: &mut RawMap, ids: Vec<OriginalRoad>) -> Resu
|
||||
|
||||
// Calculate the incline for each road here, before the road gets trimmed for intersection
|
||||
// geometry. If we did this after trimming, we'd miss some of the horizontal distance.
|
||||
for (id, road) in &mut map.roads {
|
||||
let rise = map.intersections[&id.i2].elevation - map.intersections[&id.i1].elevation;
|
||||
for (id, road) in &mut map.streets.roads {
|
||||
let rise = map.streets.intersections[&id.i2].elevation
|
||||
- map.streets.intersections[&id.i1].elevation;
|
||||
let run = road.length();
|
||||
if !(rise / run).is_finite() {
|
||||
// TODO Warn?
|
||||
|
@ -40,7 +40,7 @@ pub fn extract_osm(
|
||||
opts: &Options,
|
||||
timer: &mut Timer,
|
||||
) -> OsmExtract {
|
||||
let mut doc = crate::reader::read(osm_input_path, &map.gps_bounds, timer).unwrap();
|
||||
let mut doc = crate::reader::read(osm_input_path, &map.streets.gps_bounds, timer).unwrap();
|
||||
|
||||
// TODO Hacks to override OSM data. There's no problem upstream, but we want to accomplish
|
||||
// various things for A/B Street.
|
||||
@ -57,8 +57,8 @@ pub fn extract_osm(
|
||||
|
||||
if clip_path.is_none() {
|
||||
// Use the boundary from .osm.
|
||||
map.gps_bounds = doc.gps_bounds.clone();
|
||||
map.boundary_polygon = map.gps_bounds.to_bounds().get_rectangle();
|
||||
map.streets.gps_bounds = doc.gps_bounds.clone();
|
||||
map.streets.boundary_polygon = map.streets.gps_bounds.to_bounds().get_rectangle();
|
||||
}
|
||||
|
||||
let mut out = OsmExtract {
|
||||
@ -128,7 +128,7 @@ pub fn extract_osm(
|
||||
.is_any(osm::HIGHWAY, vec!["cycleway", "footway", "path"])
|
||||
{
|
||||
extra_footways.shapes.push(ExtraShape {
|
||||
points: map.gps_bounds.convert_back(&way.pts),
|
||||
points: map.streets.gps_bounds.convert_back(&way.pts),
|
||||
attributes: way.tags.inner().clone(),
|
||||
});
|
||||
} else if way.tags.is("natural", "coastline") && !way.tags.is("place", "island") {
|
||||
@ -188,7 +188,7 @@ pub fn extract_osm(
|
||||
abstio::write_binary(map.name.city.input_path("footways.bin"), &extra_footways);
|
||||
}
|
||||
|
||||
let boundary = map.boundary_polygon.clone().into_ring();
|
||||
let boundary = map.streets.boundary_polygon.clone().into_ring();
|
||||
|
||||
// TODO Fill this out in a separate loop to keep a mutable borrow short. Maybe do this in
|
||||
// reader, or stop doing this entirely.
|
||||
@ -576,7 +576,7 @@ fn get_area_type(tags: &Tags) -> Option<AreaType> {
|
||||
// Look for any service roads that collide with parking lots, and treat them as parking aisles
|
||||
// instead.
|
||||
fn find_parking_aisles(map: &mut RawMap, roads: &mut Vec<(WayID, Vec<Pt2D>, Tags)>) {
|
||||
let mut closest: FindClosest<usize> = FindClosest::new(&map.gps_bounds.to_bounds());
|
||||
let mut closest: FindClosest<usize> = FindClosest::new(&map.streets.gps_bounds.to_bounds());
|
||||
for (idx, lot) in map.parking_lots.iter().enumerate() {
|
||||
closest.add(idx, lot.polygon.points());
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ pub fn import(map: &mut RawMap) -> Result<()> {
|
||||
.deserialize()
|
||||
{
|
||||
let rec: Shape = rec?;
|
||||
let pt = LonLat::new(rec.shape_pt_lon, rec.shape_pt_lat).to_pt(&map.gps_bounds);
|
||||
let pt = LonLat::new(rec.shape_pt_lon, rec.shape_pt_lat).to_pt(&map.streets.gps_bounds);
|
||||
raw_shapes
|
||||
.entry(rec.shape_id)
|
||||
.or_insert_with(Vec::new)
|
||||
@ -146,8 +146,8 @@ pub fn import(map: &mut RawMap) -> Result<()> {
|
||||
{
|
||||
let rec: Stop = rec?;
|
||||
if stop_ids.contains(&rec.stop_id) {
|
||||
let position = LonLat::new(rec.stop_lon, rec.stop_lat).to_pt(&map.gps_bounds);
|
||||
if map.boundary_polygon.contains_pt(position) {
|
||||
let position = LonLat::new(rec.stop_lon, rec.stop_lat).to_pt(&map.streets.gps_bounds);
|
||||
if map.streets.boundary_polygon.contains_pt(position) {
|
||||
map.transit_stops.insert(
|
||||
rec.stop_id.0.clone(),
|
||||
RawTransitStop {
|
||||
@ -234,7 +234,7 @@ fn dump_kml(map: &RawMap) {
|
||||
|
||||
// One polyline per route
|
||||
for route in &map.transit_routes {
|
||||
let points = map.gps_bounds.convert_back(route.shape.points());
|
||||
let points = map.streets.gps_bounds.convert_back(route.shape.points());
|
||||
let mut attributes = BTreeMap::new();
|
||||
attributes.insert("long_name".to_string(), route.long_name.clone());
|
||||
attributes.insert("short_name".to_string(), route.short_name.clone());
|
||||
@ -249,7 +249,7 @@ fn dump_kml(map: &RawMap) {
|
||||
let mut attributes = BTreeMap::new();
|
||||
attributes.insert("gtfs_id".to_string(), stop.gtfs_id.clone());
|
||||
attributes.insert("name".to_string(), stop.name.clone());
|
||||
let points = vec![stop.position.to_gps(&map.gps_bounds)];
|
||||
let points = vec![stop.position.to_gps(&map.streets.gps_bounds)];
|
||||
shapes.push(ExtraShape { points, attributes });
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,7 @@ impl Options {
|
||||
inferred_sidewalks: true,
|
||||
street_parking_spot_length: Distance::meters(8.0),
|
||||
turn_on_red: true,
|
||||
find_dog_legs_experiment: false,
|
||||
},
|
||||
onstreet_parking: OnstreetParking::JustOSM,
|
||||
public_offstreet_parking: PublicOffstreetParking::None,
|
||||
@ -111,13 +112,13 @@ pub fn convert(
|
||||
) -> RawMap {
|
||||
let mut map = RawMap::blank(name);
|
||||
// Do this early. Calculating RawRoads uses DrivingSide, for example!
|
||||
map.config = opts.map_config.clone();
|
||||
map.streets.config = opts.map_config.clone();
|
||||
|
||||
if let Some(ref path) = clip_path {
|
||||
let pts = LonLat::read_osmosis_polygon(path).unwrap();
|
||||
let gps_bounds = GPSBounds::from(pts.clone());
|
||||
map.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
|
||||
map.gps_bounds = gps_bounds;
|
||||
map.streets.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
|
||||
map.streets.gps_bounds = gps_bounds;
|
||||
}
|
||||
|
||||
let extract = extract::extract_osm(&mut map, &osm_input_path, clip_path, &opts, timer);
|
||||
@ -126,7 +127,7 @@ pub fn convert(
|
||||
|
||||
// Need to do a first pass of removing cul-de-sacs here, or we wind up with loop PolyLines when
|
||||
// doing the parking hint matching.
|
||||
map.roads.retain(|r, _| r.i1 != r.i2);
|
||||
map.streets.roads.retain(|r, _| r.i1 != r.i2);
|
||||
|
||||
use_amenities(&mut map, split_output.amenities, timer);
|
||||
|
||||
@ -169,7 +170,8 @@ pub fn convert(
|
||||
}
|
||||
|
||||
fn use_amenities(map: &mut RawMap, amenities: Vec<(Pt2D, Amenity)>, timer: &mut Timer) {
|
||||
let mut closest: FindClosest<osm::OsmID> = FindClosest::new(&map.gps_bounds.to_bounds());
|
||||
let mut closest: FindClosest<osm::OsmID> =
|
||||
FindClosest::new(&map.streets.gps_bounds.to_bounds());
|
||||
for (id, b) in &map.buildings {
|
||||
closest.add(*id, b.polygon.points());
|
||||
}
|
||||
@ -191,7 +193,7 @@ fn add_extra_buildings(map: &mut RawMap, path: &str) -> Result<()> {
|
||||
let mut id = -1;
|
||||
for (polygon, _) in Polygon::from_geojson_bytes(
|
||||
&abstio::slurp_file(path)?,
|
||||
&map.gps_bounds,
|
||||
&map.streets.gps_bounds,
|
||||
require_in_bounds,
|
||||
)? {
|
||||
// Add these as new buildings, generating a new dummy OSM ID.
|
||||
@ -220,7 +222,7 @@ fn filter_crosswalks(
|
||||
) {
|
||||
// Normally we assume every road has a crosswalk, but since this map is configured to use OSM
|
||||
// crossing nodes, let's reverse that assumption.
|
||||
for road in map.roads.values_mut() {
|
||||
for road in map.streets.roads.values_mut() {
|
||||
road.crosswalk_forward = false;
|
||||
road.crosswalk_backward = false;
|
||||
}
|
||||
@ -231,7 +233,10 @@ fn filter_crosswalks(
|
||||
timer.next();
|
||||
// Some crossing nodes are outside the map boundary or otherwise not on a road that we
|
||||
// retained
|
||||
if let Some(road) = pt_to_road.get(&pt).and_then(|r| map.roads.get_mut(r)) {
|
||||
if let Some(road) = pt_to_road
|
||||
.get(&pt)
|
||||
.and_then(|r| map.streets.roads.get_mut(r))
|
||||
{
|
||||
// TODO Support cul-de-sacs and other loop roads
|
||||
if let Ok(pl) = PolyLine::new(road.osm_center_points.clone()) {
|
||||
// Crossings aren't right at an intersection. Where is this point along the center
|
||||
@ -264,7 +269,10 @@ fn use_barrier_nodes(
|
||||
) {
|
||||
for pt in barrier_nodes {
|
||||
// Many barriers are on footpaths or roads that we don't retain
|
||||
if let Some(road) = pt_to_road.get(&pt).and_then(|r| map.roads.get_mut(r)) {
|
||||
if let Some(road) = pt_to_road
|
||||
.get(&pt)
|
||||
.and_then(|r| map.streets.roads.get_mut(r))
|
||||
{
|
||||
// Filters on roads that're already car-free are redundant
|
||||
if road.is_driveable() {
|
||||
road.barrier_nodes.push(pt.to_pt2d());
|
||||
@ -292,12 +300,15 @@ fn bristol_hack(map: &mut RawMap) {
|
||||
tags.insert("maxspeed", "1 mph");
|
||||
tags.insert("bicycle", "no");
|
||||
|
||||
map.roads.insert(
|
||||
map.streets.roads.insert(
|
||||
id,
|
||||
RawRoad::new(
|
||||
vec![map.intersections[&i1].point, map.intersections[&i2].point],
|
||||
vec![
|
||||
map.streets.intersections[&i1].point,
|
||||
map.streets.intersections[&i2].point,
|
||||
],
|
||||
tags,
|
||||
&map.config,
|
||||
&map.streets.config,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
@ -16,7 +16,7 @@ pub fn apply_parking(map: &mut RawMap, opts: &Options, timer: &mut Timer) {
|
||||
}
|
||||
OnstreetParking::SomeAdditionalWhereNoData { pct } => {
|
||||
let pct = pct as i64;
|
||||
for (id, r) in map.roads.iter_mut() {
|
||||
for (id, r) in map.streets.roads.iter_mut() {
|
||||
// The 20m minimum is a rough heuristic.
|
||||
if r.osm_tags.contains_key(osm::INFERRED_PARKING)
|
||||
&& r.osm_tags
|
||||
@ -52,8 +52,8 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
|
||||
|
||||
// Match shapes with the nearest road + direction (true for forwards)
|
||||
let mut closest: FindClosest<(OriginalRoad, bool)> =
|
||||
FindClosest::new(&map.gps_bounds.to_bounds());
|
||||
for (id, r) in &map.roads {
|
||||
FindClosest::new(&map.streets.gps_bounds.to_bounds());
|
||||
for (id, r) in &map.streets.roads {
|
||||
if r.is_light_rail() || r.is_footway() || r.is_service() {
|
||||
continue;
|
||||
}
|
||||
@ -69,7 +69,7 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
|
||||
}
|
||||
|
||||
for s in shapes.shapes.into_iter() {
|
||||
let pts = map.gps_bounds.convert(&s.points);
|
||||
let pts = map.streets.gps_bounds.convert(&s.points);
|
||||
if pts.len() <= 1 {
|
||||
continue;
|
||||
}
|
||||
@ -84,7 +84,7 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
|
||||
continue;
|
||||
};
|
||||
if let Some(((r, fwds), _)) = closest.closest_pt(middle, DIRECTED_ROAD_THICKNESS * 5.0) {
|
||||
let tags = &mut map.roads.get_mut(&r).unwrap().osm_tags;
|
||||
let tags = &mut map.streets.roads.get_mut(&r).unwrap().osm_tags;
|
||||
|
||||
// Skip if the road already has this mapped.
|
||||
if !tags.contains_key(osm::INFERRED_PARKING) {
|
||||
@ -141,8 +141,8 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
|
||||
tags.insert(osm::PARKING_BOTH, value);
|
||||
}
|
||||
|
||||
let lane_specs_ltr = raw_map::get_lane_specs_ltr(tags, &map.config);
|
||||
map.roads.get_mut(&r).unwrap().lane_specs_ltr = lane_specs_ltr;
|
||||
let lane_specs_ltr = raw_map::get_lane_specs_ltr(tags, &map.streets.config);
|
||||
map.streets.roads.get_mut(&r).unwrap().lane_specs_ltr = lane_specs_ltr;
|
||||
}
|
||||
}
|
||||
timer.stop("apply parking hints");
|
||||
@ -152,7 +152,8 @@ fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
|
||||
timer.start("match offstreet parking points");
|
||||
let shapes: ExtraShapes = abstio::read_binary(path, timer);
|
||||
|
||||
let mut closest: FindClosest<osm::OsmID> = FindClosest::new(&map.gps_bounds.to_bounds());
|
||||
let mut closest: FindClosest<osm::OsmID> =
|
||||
FindClosest::new(&map.streets.gps_bounds.to_bounds());
|
||||
for (id, b) in &map.buildings {
|
||||
closest.add(*id, b.polygon.points());
|
||||
}
|
||||
@ -160,7 +161,7 @@ fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
|
||||
// TODO Another function just to use ?. Try blocks would rock.
|
||||
let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
|
||||
assert_eq!(s.points.len(), 1);
|
||||
let pt = s.points[0].to_pt(&map.gps_bounds);
|
||||
let pt = s.points[0].to_pt(&map.streets.gps_bounds);
|
||||
let (id, _) = closest.closest_pt(pt, Distance::meters(50.0))?;
|
||||
// TODO Handle parking lots.
|
||||
if !map.buildings[&id].polygon.contains_pt(pt) {
|
||||
|
@ -56,7 +56,7 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
}
|
||||
|
||||
for (pt, id) in &pt_to_intersection {
|
||||
map.intersections.insert(
|
||||
map.streets.intersections.insert(
|
||||
*id,
|
||||
RawIntersection::new(
|
||||
pt.to_pt2d(),
|
||||
@ -71,7 +71,8 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
|
||||
// Set roundabouts to their center
|
||||
for (id, point) in roundabout_centers {
|
||||
map.intersections
|
||||
map.streets
|
||||
.intersections
|
||||
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
|
||||
}
|
||||
|
||||
@ -113,9 +114,9 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
}
|
||||
|
||||
let osm_center_pts = simplify_linestring(std::mem::take(&mut pts));
|
||||
match RawRoad::new(osm_center_pts, tags, &map.config) {
|
||||
match RawRoad::new(osm_center_pts, tags, &map.streets.config) {
|
||||
Ok(road) => {
|
||||
map.roads.insert(id, road);
|
||||
map.streets.roads.insert(id, road);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Skipping {id}: {err}");
|
||||
@ -136,7 +137,7 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
// Resolve simple turn restrictions (via a node)
|
||||
let mut restrictions = Vec::new();
|
||||
for (restriction, from_osm, via_osm, to_osm) in input.simple_turn_restrictions {
|
||||
let roads = map.roads_per_intersection(via_osm);
|
||||
let roads = map.streets.roads_per_intersection(via_osm);
|
||||
// If some of the roads are missing, they were likely filtered out -- usually service
|
||||
// roads.
|
||||
if let (Some(from), Some(to)) = (
|
||||
@ -147,7 +148,8 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
}
|
||||
}
|
||||
for (from, rt, to) in restrictions {
|
||||
map.roads
|
||||
map.streets
|
||||
.roads
|
||||
.get_mut(&from)
|
||||
.unwrap()
|
||||
.turn_restrictions
|
||||
@ -159,6 +161,7 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
let mut complicated_restrictions = Vec::new();
|
||||
for (rel_osm, from_osm, via_osm, to_osm) in input.complicated_turn_restrictions {
|
||||
let via_candidates: Vec<OriginalRoad> = map
|
||||
.streets
|
||||
.roads
|
||||
.keys()
|
||||
.filter(|r| r.osm_way_id == via_osm)
|
||||
@ -175,14 +178,16 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
let via = via_candidates[0];
|
||||
|
||||
let maybe_from = map
|
||||
.streets
|
||||
.roads_per_intersection(via.i1)
|
||||
.into_iter()
|
||||
.chain(map.roads_per_intersection(via.i2).into_iter())
|
||||
.chain(map.streets.roads_per_intersection(via.i2).into_iter())
|
||||
.find(|r| r.osm_way_id == from_osm);
|
||||
let maybe_to = map
|
||||
.streets
|
||||
.roads_per_intersection(via.i1)
|
||||
.into_iter()
|
||||
.chain(map.roads_per_intersection(via.i2).into_iter())
|
||||
.chain(map.streets.roads_per_intersection(via.i2).into_iter())
|
||||
.find(|r| r.osm_way_id == to_osm);
|
||||
match (maybe_from, maybe_to) {
|
||||
(Some(from), Some(to)) => {
|
||||
@ -197,7 +202,8 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
}
|
||||
}
|
||||
for (from, via, to) in complicated_restrictions {
|
||||
map.roads
|
||||
map.streets
|
||||
.roads
|
||||
.get_mut(&from)
|
||||
.unwrap()
|
||||
.complicated_turn_restrictions
|
||||
@ -210,10 +216,16 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
|
||||
for (pt, dir) in input.traffic_signals {
|
||||
if let Some(r) = pt_to_road.get(&pt) {
|
||||
// Example: https://www.openstreetmap.org/node/26734224
|
||||
if !map.roads[r].osm_tags.is(osm::HIGHWAY, "construction") {
|
||||
if !map.streets.roads[r]
|
||||
.osm_tags
|
||||
.is(osm::HIGHWAY, "construction")
|
||||
{
|
||||
let i = if dir == Direction::Fwd { r.i2 } else { r.i1 };
|
||||
map.intersections.get_mut(&i).unwrap().intersection_type =
|
||||
IntersectionType::TrafficSignal;
|
||||
map.streets
|
||||
.intersections
|
||||
.get_mut(&i)
|
||||
.unwrap()
|
||||
.intersection_type = IntersectionType::TrafficSignal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ pub async fn import_extra_data(
|
||||
download_kml(
|
||||
map.get_city_name().input_path("planning_areas.bin"),
|
||||
"https://tsb-opendata.s3.eu-central-1.amazonaws.com/lor_planungsgraeume/lor_planungsraeume.kml",
|
||||
&map.gps_bounds,
|
||||
&map.streets.gps_bounds,
|
||||
// Keep partly out-of-bounds polygons
|
||||
false,
|
||||
timer
|
||||
|
@ -32,6 +32,20 @@ pub fn config_for_map(name: &MapName) -> convert_osm::Options {
|
||||
Distance::meters(8.0)
|
||||
},
|
||||
turn_on_red: name.city.country == "us" && name.city.city != "nyc",
|
||||
find_dog_legs_experiment: vec![
|
||||
MapName::seattle("montlake"),
|
||||
MapName::seattle("downtown"),
|
||||
MapName::seattle("lakeslice"),
|
||||
MapName::new("us", "phoenix", "tempe"),
|
||||
MapName::new("gb", "bristol", "east"),
|
||||
//MapName::new("gb", "leeds", "north"),
|
||||
//MapName::new("gb", "london", "camden"),
|
||||
MapName::new("gb", "london", "kennington"),
|
||||
//MapName::new("gb", "london", "southwark"),
|
||||
//MapName::new("gb", "manchester", "levenshulme"),
|
||||
MapName::new("pl", "krakow", "center"),
|
||||
]
|
||||
.contains(name),
|
||||
},
|
||||
onstreet_parking: match name.city.city.as_ref() {
|
||||
"seattle" => {
|
||||
|
@ -30,7 +30,7 @@ pub async fn import_collision_data(
|
||||
// Always do this, it's idempotent and fast
|
||||
let shapes = kml::ExtraShapes::load_csv(
|
||||
path_shared_input("Road Safety Data - Accidents 2019.csv"),
|
||||
&map.gps_bounds,
|
||||
&map.streets.gps_bounds,
|
||||
timer,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -45,12 +45,13 @@ pub struct RawToMapOptions {
|
||||
|
||||
impl Map {
|
||||
pub fn create_from_raw(mut raw: RawMap, opts: RawToMapOptions, timer: &mut Timer) -> Map {
|
||||
raw.run_all_simplifications(opts.consolidate_all_intersections, true, timer);
|
||||
raw.streets
|
||||
.run_all_simplifications(opts.consolidate_all_intersections, true, timer);
|
||||
|
||||
timer.start("raw_map to InitialMap");
|
||||
let gps_bounds = raw.gps_bounds.clone();
|
||||
let gps_bounds = raw.streets.gps_bounds.clone();
|
||||
let bounds = gps_bounds.to_bounds();
|
||||
let initial_map = initial::InitialMap::new(&raw, &bounds, timer);
|
||||
let initial_map = initial::InitialMap::new(&raw.streets, &bounds, timer);
|
||||
timer.stop("raw_map to InitialMap");
|
||||
|
||||
let mut map = Map {
|
||||
@ -62,12 +63,12 @@ impl Map {
|
||||
areas: Vec::new(),
|
||||
parking_lots: Vec::new(),
|
||||
zones: Vec::new(),
|
||||
boundary_polygon: raw.boundary_polygon.clone(),
|
||||
boundary_polygon: raw.streets.boundary_polygon.clone(),
|
||||
stop_signs: BTreeMap::new(),
|
||||
traffic_signals: BTreeMap::new(),
|
||||
gps_bounds,
|
||||
bounds,
|
||||
config: raw.config.clone(),
|
||||
config: raw.streets.config.clone(),
|
||||
pathfinder: Pathfinder::empty(),
|
||||
pathfinder_dirty: false,
|
||||
routing_params: RoutingParams::default(),
|
||||
@ -99,7 +100,9 @@ impl Map {
|
||||
incoming_lanes: Vec::new(),
|
||||
outgoing_lanes: Vec::new(),
|
||||
roads: i.roads.iter().map(|id| road_id_mapping[id]).collect(),
|
||||
merged: !raw.intersections[&i.id].trim_roads_for_merging.is_empty(),
|
||||
merged: !raw.streets.intersections[&i.id]
|
||||
.trim_roads_for_merging
|
||||
.is_empty(),
|
||||
});
|
||||
intersection_id_mapping.insert(i.id, id);
|
||||
}
|
||||
@ -112,7 +115,7 @@ impl Map {
|
||||
let i1 = intersection_id_mapping[&r.src_i];
|
||||
let i2 = intersection_id_mapping[&r.dst_i];
|
||||
|
||||
let raw_road = &raw.roads[&r.id];
|
||||
let raw_road = &raw.streets.roads[&r.id];
|
||||
let barrier_nodes = snap_nodes_to_line(&raw_road.barrier_nodes, &r.trimmed_center_pts);
|
||||
let mut road = Road {
|
||||
id: road_id,
|
||||
|
@ -152,6 +152,7 @@ impl Map {
|
||||
inferred_sidewalks: true,
|
||||
street_parking_spot_length: Distance::meters(8.0),
|
||||
turn_on_red: true,
|
||||
find_dog_legs_experiment: false,
|
||||
},
|
||||
pathfinder: Pathfinder::empty(),
|
||||
pathfinder_dirty: false,
|
||||
|
@ -5,16 +5,10 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aabb-quadtree = "0.1.0"
|
||||
abstio = { path = "../abstio" }
|
||||
abstutil = { path = "../abstutil" }
|
||||
anyhow = "1.0.38"
|
||||
geojson = { version = "0.22.2", features = ["geo-types"] }
|
||||
geom = { path = "../geom" }
|
||||
kml = { path = "../kml" }
|
||||
log = "0.4.14"
|
||||
petgraph = { version = "0.6.0" }
|
||||
serde = "1.0.123"
|
||||
serde_json = "1.0.61"
|
||||
street_network = { path = "../street_network" }
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
|
@ -2,51 +2,25 @@
|
||||
//! structure is useful to iterate quickly on parts of the map importing pipeline without having to
|
||||
//! constantly read .osm files, and to visualize the intermediate state with map_editor.
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use petgraph::graphmap::DiGraphMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use abstio::{CityName, MapName};
|
||||
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
|
||||
use geom::{Angle, Distance, GPSBounds, PolyLine, Polygon, Pt2D};
|
||||
use geom::{PolyLine, Polygon, Pt2D};
|
||||
|
||||
pub use self::geometry::{intersection_polygon, InputRoad};
|
||||
pub use self::lane_specs::get_lane_specs_ltr;
|
||||
pub use self::types::{
|
||||
Amenity, AmenityType, AreaType, BufferType, Direction, DrivingSide, IntersectionType, LaneSpec,
|
||||
LaneType, MapConfig, NamePerLanguage, NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS,
|
||||
};
|
||||
// Re-export everything for refactoring convenience
|
||||
pub use street_network::*;
|
||||
|
||||
pub use self::types::{Amenity, AmenityType, AreaType};
|
||||
|
||||
mod edit;
|
||||
mod export;
|
||||
pub mod geometry;
|
||||
pub mod initial;
|
||||
mod lane_specs;
|
||||
pub mod osm;
|
||||
mod transform;
|
||||
mod types;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RawMap {
|
||||
pub name: MapName,
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub roads: BTreeMap<OriginalRoad, RawRoad>,
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub intersections: BTreeMap<osm::NodeID, RawIntersection>,
|
||||
pub streets: StreetNetwork,
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
@ -61,127 +35,19 @@ pub struct RawMap {
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub transit_stops: BTreeMap<String, RawTransitStop>,
|
||||
|
||||
pub boundary_polygon: Polygon,
|
||||
pub gps_bounds: GPSBounds,
|
||||
pub config: MapConfig,
|
||||
}
|
||||
|
||||
/// A way to refer to roads across many maps and over time. Also trivial to relate with OSM to find
|
||||
/// upstream problems.
|
||||
//
|
||||
// - Using LonLat is more indirect, and f64's need to be trimmed and compared carefully with epsilon
|
||||
// checks.
|
||||
// - TODO Look at some stable ID standard like linear referencing
|
||||
// (https://github.com/opentraffic/architecture/issues/1).
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct OriginalRoad {
|
||||
pub osm_way_id: osm::WayID,
|
||||
pub i1: osm::NodeID,
|
||||
pub i2: osm::NodeID,
|
||||
}
|
||||
|
||||
impl fmt::Display for OriginalRoad {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"OriginalRoad({} from {} to {}",
|
||||
self.osm_way_id, self.i1, self.i2
|
||||
)
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for OriginalRoad {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginalRoad {
|
||||
pub fn new(way: i64, (i1, i2): (i64, i64)) -> OriginalRoad {
|
||||
OriginalRoad {
|
||||
osm_way_id: osm::WayID(way),
|
||||
i1: osm::NodeID(i1),
|
||||
i2: osm::NodeID(i2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the OriginalRoad in a way that can be copied to Rust code.
|
||||
pub fn as_string_code(&self) -> String {
|
||||
format!(
|
||||
"OriginalRoad::new({}, ({}, {}))",
|
||||
self.osm_way_id.0, self.i1.0, self.i2.0
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_common_endpoint(&self, other: OriginalRoad) -> bool {
|
||||
if self.i1 == other.i1 || self.i1 == other.i2 {
|
||||
return true;
|
||||
}
|
||||
if self.i2 == other.i1 || self.i2 == other.i2 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// TODO Doesn't handle two roads between the same pair of intersections
|
||||
pub fn common_endpt(&self, other: OriginalRoad) -> osm::NodeID {
|
||||
#![allow(clippy::suspicious_operation_groupings)]
|
||||
if self.i1 == other.i1 || self.i1 == other.i2 {
|
||||
return self.i1;
|
||||
}
|
||||
if self.i2 == other.i1 || self.i2 == other.i2 {
|
||||
return self.i2;
|
||||
}
|
||||
panic!("{:?} and {:?} have no common_endpt", self, other);
|
||||
}
|
||||
}
|
||||
|
||||
impl RawMap {
|
||||
pub fn blank(name: MapName) -> RawMap {
|
||||
RawMap {
|
||||
name,
|
||||
roads: BTreeMap::new(),
|
||||
intersections: BTreeMap::new(),
|
||||
streets: StreetNetwork::blank(),
|
||||
buildings: BTreeMap::new(),
|
||||
areas: Vec::new(),
|
||||
parking_lots: Vec::new(),
|
||||
parking_aisles: Vec::new(),
|
||||
transit_routes: Vec::new(),
|
||||
transit_stops: BTreeMap::new(),
|
||||
// Some nonsense thing
|
||||
boundary_polygon: Polygon::rectangle(1.0, 1.0),
|
||||
gps_bounds: GPSBounds::new(),
|
||||
config: MapConfig {
|
||||
driving_side: DrivingSide::Right,
|
||||
bikes_can_use_bus_lanes: true,
|
||||
inferred_sidewalks: true,
|
||||
street_parking_spot_length: Distance::meters(8.0),
|
||||
turn_on_red: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Might be better to maintain this instead of doing a search everytime.
|
||||
pub fn roads_per_intersection(&self, i: osm::NodeID) -> Vec<OriginalRoad> {
|
||||
let mut results = Vec::new();
|
||||
for id in self.roads.keys() {
|
||||
if id.i1 == i || id.i2 == i {
|
||||
results.push(*id);
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
pub fn new_osm_node_id(&self, start: i64) -> osm::NodeID {
|
||||
assert!(start < 0);
|
||||
// Slow, but deterministic.
|
||||
let mut osm_node_id = start;
|
||||
loop {
|
||||
if self.intersections.keys().any(|i| i.0 == osm_node_id) {
|
||||
osm_node_id -= 1;
|
||||
} else {
|
||||
return osm::NodeID(osm_node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +59,11 @@ impl RawMap {
|
||||
loop {
|
||||
let candidate = osm::WayID(osm_way_id);
|
||||
// TODO Doesn't handle collisions with areas or parking lots
|
||||
if self.roads.keys().any(|r| r.osm_way_id.0 == osm_way_id)
|
||||
if self
|
||||
.streets
|
||||
.roads
|
||||
.keys()
|
||||
.any(|r| r.osm_way_id.0 == osm_way_id)
|
||||
|| self
|
||||
.buildings
|
||||
.keys()
|
||||
@ -206,70 +76,6 @@ impl RawMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// (Intersection polygon, polygons for roads, list of labeled polygons to debug)
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn preview_intersection(
|
||||
&self,
|
||||
id: osm::NodeID,
|
||||
) -> Result<(Polygon, Vec<Polygon>, Vec<(String, Polygon)>)> {
|
||||
let mut input_roads = Vec::new();
|
||||
for r in self.roads_per_intersection(id) {
|
||||
input_roads.push(initial::Road::new(self, r).to_input_road());
|
||||
}
|
||||
let results = intersection_polygon(
|
||||
id,
|
||||
input_roads,
|
||||
// This'll be empty unless we've called merge_short_road
|
||||
&self.intersections[&id].trim_roads_for_merging,
|
||||
)?;
|
||||
Ok((
|
||||
results.intersection_polygon,
|
||||
results
|
||||
.trimmed_center_pts
|
||||
.into_values()
|
||||
.map(|(pl, half_width)| pl.make_polygons(2.0 * half_width))
|
||||
.collect(),
|
||||
results.debug,
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate the trimmed `PolyLine` for a single RawRoad by calculating both intersections
|
||||
pub fn trimmed_road_geometry(&self, road_id: OriginalRoad) -> Result<PolyLine> {
|
||||
// First trim at one of the endpoints
|
||||
let trimmed_center_pts = {
|
||||
let mut input_roads = Vec::new();
|
||||
for r in self.roads_per_intersection(road_id.i1) {
|
||||
input_roads.push(initial::Road::new(self, r).to_input_road());
|
||||
}
|
||||
let mut results = intersection_polygon(
|
||||
road_id.i1,
|
||||
input_roads,
|
||||
// TODO Not sure if we should use this or not
|
||||
&BTreeMap::new(),
|
||||
)?;
|
||||
results.trimmed_center_pts.remove(&road_id).unwrap().0
|
||||
};
|
||||
|
||||
// Now the second
|
||||
{
|
||||
let mut input_roads = Vec::new();
|
||||
for r in self.roads_per_intersection(road_id.i2) {
|
||||
let mut road = initial::Road::new(self, r).to_input_road();
|
||||
if r == road_id {
|
||||
road.center_pts = trimmed_center_pts.clone();
|
||||
}
|
||||
input_roads.push(road);
|
||||
}
|
||||
let mut results = intersection_polygon(
|
||||
road_id.i2,
|
||||
input_roads,
|
||||
// TODO Not sure if we should use this or not
|
||||
&BTreeMap::new(),
|
||||
)?;
|
||||
Ok(results.trimmed_center_pts.remove(&road_id).unwrap().0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
abstio::write_binary(abstio::path_raw_map(&self.name), self)
|
||||
}
|
||||
@ -279,262 +85,6 @@ impl RawMap {
|
||||
}
|
||||
}
|
||||
|
||||
// Mutations and supporting queries
|
||||
impl RawMap {
|
||||
pub fn can_delete_intersection(&self, i: osm::NodeID) -> bool {
|
||||
self.roads_per_intersection(i).is_empty()
|
||||
}
|
||||
|
||||
pub fn delete_intersection(&mut self, id: osm::NodeID) {
|
||||
if !self.can_delete_intersection(id) {
|
||||
panic!(
|
||||
"Can't delete_intersection {}, must have roads connected",
|
||||
id
|
||||
);
|
||||
}
|
||||
self.intersections.remove(&id).unwrap();
|
||||
}
|
||||
|
||||
pub fn move_intersection(&mut self, id: osm::NodeID, point: Pt2D) -> Option<Vec<OriginalRoad>> {
|
||||
self.intersections.get_mut(&id).unwrap().point = point;
|
||||
|
||||
// Update all the roads.
|
||||
let mut fixed = Vec::new();
|
||||
for r in self.roads_per_intersection(id) {
|
||||
fixed.push(r);
|
||||
let road = self.roads.get_mut(&r).unwrap();
|
||||
if r.i1 == id {
|
||||
road.osm_center_points[0] = point;
|
||||
} else {
|
||||
assert_eq!(r.i2, id);
|
||||
*road.osm_center_points.last_mut().unwrap() = point;
|
||||
}
|
||||
}
|
||||
|
||||
Some(fixed)
|
||||
}
|
||||
|
||||
pub fn closest_intersection(&self, pt: Pt2D) -> osm::NodeID {
|
||||
self.intersections
|
||||
.iter()
|
||||
.min_by_key(|(_, i)| i.point.dist_to(pt))
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn path_dist_to(&self, from: osm::NodeID, to: osm::NodeID) -> Option<Distance> {
|
||||
let mut graph = DiGraphMap::new();
|
||||
for (id, r) in &self.roads {
|
||||
graph.add_edge(id.i1, id.i2, id);
|
||||
if r.oneway_for_driving().is_none() {
|
||||
graph.add_edge(id.i2, id.i1, id);
|
||||
}
|
||||
}
|
||||
petgraph::algo::dijkstra(&graph, from, Some(to), |(_, _, r)| {
|
||||
// TODO Expensive!
|
||||
self.roads[r].length()
|
||||
})
|
||||
.get(&to)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RawRoad {
|
||||
/// This is effectively a PolyLine, except there's a case where we need to plumb forward
|
||||
/// cul-de-sac roads for roundabout handling. No transformation of these points whatsoever has
|
||||
/// happened.
|
||||
pub osm_center_points: Vec<Pt2D>,
|
||||
pub osm_tags: Tags,
|
||||
pub turn_restrictions: Vec<(RestrictionType, OriginalRoad)>,
|
||||
/// (via, to). For turn restrictions where 'via' is an entire road. Only BanTurns.
|
||||
pub complicated_turn_restrictions: Vec<(OriginalRoad, OriginalRoad)>,
|
||||
pub percent_incline: f64,
|
||||
/// Is there a tagged crosswalk near each end of the road?
|
||||
pub crosswalk_forward: bool,
|
||||
pub crosswalk_backward: bool,
|
||||
/// Barrier nodes along this road's original center line.
|
||||
// TODO Preserving these across transformations (especially merging dual carriageways!) could
|
||||
// be really hard. It might be better to split the road into two pieces to match the more often
|
||||
// used OSM style.
|
||||
pub barrier_nodes: Vec<Pt2D>,
|
||||
|
||||
/// Derived from osm_tags. Not automatically updated.
|
||||
pub lane_specs_ltr: Vec<LaneSpec>,
|
||||
}
|
||||
|
||||
impl RawRoad {
|
||||
pub fn new(osm_center_points: Vec<Pt2D>, osm_tags: Tags, config: &MapConfig) -> Result<Self> {
|
||||
// Just flush out errors immediately.
|
||||
// TODO Store the PolyLine, not a Vec<Pt2D>
|
||||
let _ = PolyLine::new(osm_center_points.clone())?;
|
||||
|
||||
let lane_specs_ltr = get_lane_specs_ltr(&osm_tags, config);
|
||||
|
||||
Ok(Self {
|
||||
osm_center_points,
|
||||
osm_tags,
|
||||
turn_restrictions: Vec::new(),
|
||||
complicated_turn_restrictions: Vec::new(),
|
||||
percent_incline: 0.0,
|
||||
// Start assuming there's a crosswalk everywhere, and maybe filter it down
|
||||
// later
|
||||
crosswalk_forward: true,
|
||||
crosswalk_backward: true,
|
||||
barrier_nodes: Vec::new(),
|
||||
|
||||
lane_specs_ltr,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO For the moment, treating all rail things as light rail
|
||||
pub fn is_light_rail(&self) -> bool {
|
||||
self.osm_tags.is_any("railway", vec!["light_rail", "rail"])
|
||||
}
|
||||
|
||||
pub fn is_footway(&self) -> bool {
|
||||
self.osm_tags.is_any(
|
||||
osm::HIGHWAY,
|
||||
vec![
|
||||
"cycleway",
|
||||
"footway",
|
||||
"path",
|
||||
"pedestrian",
|
||||
"steps",
|
||||
"track",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_service(&self) -> bool {
|
||||
self.osm_tags.is(osm::HIGHWAY, "service")
|
||||
}
|
||||
|
||||
pub fn is_cycleway(&self) -> bool {
|
||||
// Don't repeat the logic looking at the tags, just see what lanes we'll create
|
||||
let mut bike = false;
|
||||
for spec in &self.lane_specs_ltr {
|
||||
if spec.lt == LaneType::Biking {
|
||||
bike = true;
|
||||
} else if spec.lt != LaneType::Shoulder {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bike
|
||||
}
|
||||
|
||||
pub fn is_driveable(&self) -> bool {
|
||||
self.lane_specs_ltr
|
||||
.iter()
|
||||
.any(|spec| spec.lt == LaneType::Driving)
|
||||
}
|
||||
|
||||
pub fn oneway_for_driving(&self) -> Option<Direction> {
|
||||
LaneSpec::oneway_for_driving(&self.lane_specs_ltr)
|
||||
}
|
||||
|
||||
/// Points from first to last point. Undefined for loops.
|
||||
pub fn angle(&self) -> Angle {
|
||||
self.osm_center_points[0].angle_to(*self.osm_center_points.last().unwrap())
|
||||
}
|
||||
|
||||
pub fn length(&self) -> Distance {
|
||||
PolyLine::unchecked_new(self.osm_center_points.clone()).length()
|
||||
}
|
||||
|
||||
pub fn get_zorder(&self) -> isize {
|
||||
if let Some(layer) = self.osm_tags.get("layer") {
|
||||
match layer.parse::<f64>() {
|
||||
// Just drop .5 for now
|
||||
Ok(l) => l as isize,
|
||||
Err(_) => {
|
||||
warn!("Weird layer={} on {}", layer, self.osm_url());
|
||||
0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the corrected (but untrimmed) center and total width for a road
|
||||
pub fn untrimmed_road_geometry(&self) -> (PolyLine, Distance) {
|
||||
let mut total_width = Distance::ZERO;
|
||||
let mut sidewalk_right = None;
|
||||
let mut sidewalk_left = None;
|
||||
for l in &self.lane_specs_ltr {
|
||||
total_width += l.width;
|
||||
if l.lt.is_walkable() {
|
||||
if l.dir == Direction::Back {
|
||||
sidewalk_left = Some(l.width);
|
||||
} else {
|
||||
sidewalk_right = Some(l.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a sidewalk on only one side, adjust the true center of the road.
|
||||
// TODO I don't remember the rationale for doing this in the first place. What if there's a
|
||||
// shoulder and a sidewalk of different widths? We don't do anything then
|
||||
let mut true_center = match PolyLine::new(self.osm_center_points.clone()) {
|
||||
Ok(pl) => pl,
|
||||
Err(err) => panic!(
|
||||
"untrimmed_road_geometry of {} failed: {}",
|
||||
self.osm_url(),
|
||||
err
|
||||
),
|
||||
};
|
||||
match (sidewalk_right, sidewalk_left) {
|
||||
(Some(w), None) => {
|
||||
true_center = true_center.must_shift_right(w / 2.0);
|
||||
}
|
||||
(None, Some(w)) => {
|
||||
true_center = true_center.must_shift_right(w / 2.0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
(true_center, total_width)
|
||||
}
|
||||
|
||||
pub fn osm_url(&self) -> String {
|
||||
// Since we don't store an OriginalRoad (since we may need to update it during
|
||||
// transformations), this may be convenient
|
||||
format!(
|
||||
"http://openstreetmap.org/way/{}",
|
||||
self.osm_tags.get(osm::OSM_WAY_ID).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RawIntersection {
|
||||
/// Represents the original place where OSM center-lines meet. This may be meaningless beyond
|
||||
/// RawMap; roads and intersections get merged and deleted.
|
||||
pub point: Pt2D,
|
||||
pub intersection_type: IntersectionType,
|
||||
pub elevation: Distance,
|
||||
|
||||
// true if src_i matches this intersection (or the deleted/consolidated one, whatever)
|
||||
pub trim_roads_for_merging: BTreeMap<(osm::WayID, bool), Pt2D>,
|
||||
}
|
||||
|
||||
impl RawIntersection {
|
||||
pub fn new(point: Pt2D, intersection_type: IntersectionType) -> Self {
|
||||
Self {
|
||||
point,
|
||||
intersection_type,
|
||||
// Filled out later
|
||||
elevation: Distance::ZERO,
|
||||
trim_roads_for_merging: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_border(&self) -> bool {
|
||||
self.intersection_type == IntersectionType::Border
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawBuilding {
|
||||
pub polygon: Polygon,
|
||||
@ -559,33 +109,6 @@ pub struct RawParkingLot {
|
||||
pub osm_tags: Tags,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum RestrictionType {
|
||||
BanTurns,
|
||||
OnlyAllowTurns,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TurnRestriction(pub OriginalRoad, pub RestrictionType, pub OriginalRoad);
|
||||
|
||||
impl RestrictionType {
|
||||
pub fn new(restriction: &str) -> Option<RestrictionType> {
|
||||
// TODO There's a huge space of things not represented yet: time conditions, bus-only, no
|
||||
// right turn on red...
|
||||
|
||||
// There are so many possibilities:
|
||||
// https://taginfo.openstreetmap.org/keys/restriction#values
|
||||
// Just attempt to bucket into allow / deny.
|
||||
if restriction.contains("no_") || restriction == "psv" {
|
||||
Some(RestrictionType::BanTurns)
|
||||
} else if restriction.contains("only_") {
|
||||
Some(RestrictionType::OnlyAllowTurns)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawTransitRoute {
|
||||
pub long_name: String,
|
||||
|
@ -1,19 +1,9 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{Display, EnumIter, EnumString};
|
||||
|
||||
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
|
||||
use geom::Distance;
|
||||
|
||||
use crate::osm;
|
||||
|
||||
pub const NORMAL_LANE_THICKNESS: Distance = Distance::const_meters(2.5);
|
||||
const SERVICE_ROAD_LANE_THICKNESS: Distance = Distance::const_meters(1.5);
|
||||
pub const SIDEWALK_THICKNESS: Distance = Distance::const_meters(1.5);
|
||||
const SHOULDER_THICKNESS: Distance = Distance::const_meters(0.5);
|
||||
use abstutil::Tags;
|
||||
use street_network::NamePerLanguage;
|
||||
|
||||
/// A business located inside a building.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@ -190,49 +180,6 @@ impl AmenityType {
|
||||
}
|
||||
}
|
||||
|
||||
/// None corresponds to the native name
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct NamePerLanguage(
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub(crate) BTreeMap<Option<String>, String>,
|
||||
);
|
||||
|
||||
impl NamePerLanguage {
|
||||
pub fn get(&self, lang: Option<&String>) -> &String {
|
||||
// TODO Can we avoid this clone?
|
||||
let lang = lang.cloned();
|
||||
if let Some(name) = self.0.get(&lang) {
|
||||
return name;
|
||||
}
|
||||
&self.0[&None]
|
||||
}
|
||||
|
||||
pub fn new(tags: &Tags) -> Option<NamePerLanguage> {
|
||||
let native_name = tags.get(osm::NAME)?;
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(None, native_name.to_string());
|
||||
for (k, v) in tags.inner() {
|
||||
if let Some(lang) = k.strip_prefix("name:") {
|
||||
map.insert(Some(lang.to_string()), v.to_string());
|
||||
}
|
||||
}
|
||||
Some(NamePerLanguage(map))
|
||||
}
|
||||
|
||||
pub fn unnamed() -> NamePerLanguage {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(None, "unnamed".to_string());
|
||||
NamePerLanguage(map)
|
||||
}
|
||||
|
||||
pub fn languages(&self) -> Vec<&String> {
|
||||
self.0.keys().flatten().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AreaType {
|
||||
Park,
|
||||
@ -244,343 +191,3 @@ pub enum AreaType {
|
||||
/// Not from OSM. A user-specified area to focus on.
|
||||
StudyArea,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Direction {
|
||||
Fwd,
|
||||
Back,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn opposite(self) -> Direction {
|
||||
match self {
|
||||
Direction::Fwd => Direction::Back,
|
||||
Direction::Back => Direction::Fwd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Direction::Fwd => write!(f, "forwards"),
|
||||
Direction::Back => write!(f, "backwards"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MapConfig {
|
||||
/// If true, driving happens on the right side of the road (USA). If false, on the left
|
||||
/// (Australia).
|
||||
pub driving_side: DrivingSide,
|
||||
pub bikes_can_use_bus_lanes: bool,
|
||||
/// If true, roads without explicitly tagged sidewalks may have sidewalks or shoulders. If
|
||||
/// false, no sidewalks will be inferred if not tagged in OSM, and separate sidewalks will be
|
||||
/// included.
|
||||
pub inferred_sidewalks: bool,
|
||||
/// Street parking is divided into spots of this length. 8 meters is a reasonable default, but
|
||||
/// people in some regions might be more accustomed to squeezing into smaller spaces. This
|
||||
/// value can be smaller than the hardcoded maximum car length; cars may render on top of each
|
||||
/// other, but otherwise the simulation doesn't care.
|
||||
pub street_parking_spot_length: Distance,
|
||||
/// If true, turns on red which do not conflict crossing traffic ('right on red') are allowed
|
||||
pub turn_on_red: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
pub enum DrivingSide {
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum IntersectionType {
|
||||
StopSign,
|
||||
TrafficSignal,
|
||||
Border,
|
||||
Construction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum LaneType {
|
||||
Driving,
|
||||
Parking,
|
||||
Sidewalk,
|
||||
// Walkable like a Sidewalk, but very narrow. Used to model pedestrians walking on roads
|
||||
// without sidewalks.
|
||||
Shoulder,
|
||||
Biking,
|
||||
Bus,
|
||||
SharedLeftTurn,
|
||||
Construction,
|
||||
LightRail,
|
||||
Buffer(BufferType),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BufferType {
|
||||
/// Just paint!
|
||||
Stripes,
|
||||
/// Flex posts, wands, cones, other "weak" forms of protection. Can weave through them.
|
||||
FlexPosts,
|
||||
/// Sturdier planters, with gaps.
|
||||
Planters,
|
||||
/// Solid barrier, no gaps.
|
||||
JerseyBarrier,
|
||||
/// A raised curb
|
||||
Curb,
|
||||
}
|
||||
|
||||
impl LaneType {
|
||||
pub fn is_for_moving_vehicles(self) -> bool {
|
||||
match self {
|
||||
LaneType::Driving => true,
|
||||
LaneType::Biking => true,
|
||||
LaneType::Bus => true,
|
||||
LaneType::Parking => false,
|
||||
LaneType::Sidewalk => false,
|
||||
LaneType::Shoulder => false,
|
||||
LaneType::SharedLeftTurn => false,
|
||||
LaneType::Construction => false,
|
||||
LaneType::LightRail => true,
|
||||
LaneType::Buffer(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_any_movement(self) -> bool {
|
||||
match self {
|
||||
LaneType::Driving => true,
|
||||
LaneType::Biking => true,
|
||||
LaneType::Bus => true,
|
||||
LaneType::Parking => false,
|
||||
LaneType::Sidewalk => true,
|
||||
LaneType::Shoulder => true,
|
||||
LaneType::SharedLeftTurn => false,
|
||||
LaneType::Construction => false,
|
||||
LaneType::LightRail => true,
|
||||
LaneType::Buffer(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_walkable(self) -> bool {
|
||||
self == LaneType::Sidewalk || self == LaneType::Shoulder
|
||||
}
|
||||
|
||||
pub fn describe(self) -> &'static str {
|
||||
match self {
|
||||
LaneType::Driving => "a general-purpose driving lane",
|
||||
LaneType::Biking => "a protected bike lane",
|
||||
LaneType::Bus => "a bus-only lane",
|
||||
LaneType::Parking => "an on-street parking lane",
|
||||
LaneType::Sidewalk => "a sidewalk",
|
||||
LaneType::Shoulder => "a shoulder",
|
||||
LaneType::SharedLeftTurn => "a shared left-turn lane",
|
||||
LaneType::Construction => "a lane that's closed for construction",
|
||||
LaneType::LightRail => "a light rail track",
|
||||
LaneType::Buffer(BufferType::Stripes) => "striped pavement",
|
||||
LaneType::Buffer(BufferType::FlexPosts) => "flex post barriers",
|
||||
LaneType::Buffer(BufferType::Planters) => "planter barriers",
|
||||
LaneType::Buffer(BufferType::JerseyBarrier) => "a Jersey barrier",
|
||||
LaneType::Buffer(BufferType::Curb) => "a raised curb",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_name(self) -> &'static str {
|
||||
match self {
|
||||
LaneType::Driving => "driving lane",
|
||||
LaneType::Biking => "bike lane",
|
||||
LaneType::Bus => "bus lane",
|
||||
LaneType::Parking => "parking lane",
|
||||
LaneType::Sidewalk => "sidewalk",
|
||||
LaneType::Shoulder => "shoulder",
|
||||
LaneType::SharedLeftTurn => "left-turn lane",
|
||||
LaneType::Construction => "construction",
|
||||
LaneType::LightRail => "light rail track",
|
||||
LaneType::Buffer(BufferType::Stripes) => "stripes",
|
||||
LaneType::Buffer(BufferType::FlexPosts) => "flex posts",
|
||||
LaneType::Buffer(BufferType::Planters) => "planters",
|
||||
LaneType::Buffer(BufferType::JerseyBarrier) => "Jersey barrier",
|
||||
LaneType::Buffer(BufferType::Curb) => "curb",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_short_name(x: &str) -> Option<LaneType> {
|
||||
match x {
|
||||
"driving lane" => Some(LaneType::Driving),
|
||||
"bike lane" => Some(LaneType::Biking),
|
||||
"bus lane" => Some(LaneType::Bus),
|
||||
"parking lane" => Some(LaneType::Parking),
|
||||
"sidewalk" => Some(LaneType::Sidewalk),
|
||||
"shoulder" => Some(LaneType::Shoulder),
|
||||
"left-turn lane" => Some(LaneType::SharedLeftTurn),
|
||||
"construction" => Some(LaneType::Construction),
|
||||
"light rail track" => Some(LaneType::LightRail),
|
||||
"stripes" => Some(LaneType::Buffer(BufferType::Stripes)),
|
||||
"flex posts" => Some(LaneType::Buffer(BufferType::FlexPosts)),
|
||||
"planters" => Some(LaneType::Buffer(BufferType::Planters)),
|
||||
"Jersey barrier" => Some(LaneType::Buffer(BufferType::JerseyBarrier)),
|
||||
"curb" => Some(LaneType::Buffer(BufferType::Curb)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the lane type as a single character, for use in tests.
|
||||
pub fn to_char(self) -> char {
|
||||
match self {
|
||||
LaneType::Driving => 'd',
|
||||
LaneType::Biking => 'b',
|
||||
LaneType::Bus => 'B',
|
||||
LaneType::Parking => 'p',
|
||||
LaneType::Sidewalk => 's',
|
||||
LaneType::Shoulder => 'S',
|
||||
LaneType::SharedLeftTurn => 'C',
|
||||
LaneType::Construction => 'x',
|
||||
LaneType::LightRail => 'l',
|
||||
LaneType::Buffer(_) => '|',
|
||||
}
|
||||
}
|
||||
|
||||
/// The inverse of `to_char`. Always picks one buffer type. Panics on invalid input.
|
||||
pub fn from_char(x: char) -> LaneType {
|
||||
match x {
|
||||
'd' => LaneType::Driving,
|
||||
'b' => LaneType::Biking,
|
||||
'B' => LaneType::Bus,
|
||||
'p' => LaneType::Parking,
|
||||
's' => LaneType::Sidewalk,
|
||||
'S' => LaneType::Shoulder,
|
||||
'C' => LaneType::SharedLeftTurn,
|
||||
'x' => LaneType::Construction,
|
||||
'l' => LaneType::LightRail,
|
||||
'|' => LaneType::Buffer(BufferType::FlexPosts),
|
||||
_ => panic!("from_char({}) undefined", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LaneSpec {
|
||||
pub lt: LaneType,
|
||||
pub dir: Direction,
|
||||
pub width: Distance,
|
||||
}
|
||||
|
||||
impl LaneSpec {
|
||||
/// For a given lane type, returns some likely widths. This may depend on the type of the road,
|
||||
/// so the OSM tags are also passed in. The first value returned will be used as a default.
|
||||
pub fn typical_lane_widths(lt: LaneType, tags: &Tags) -> Vec<(Distance, &'static str)> {
|
||||
// These're cobbled together from various sources
|
||||
match lt {
|
||||
// https://en.wikipedia.org/wiki/Lane#Lane_width
|
||||
LaneType::Driving => {
|
||||
let mut choices = vec![
|
||||
(Distance::feet(8.0), "narrow"),
|
||||
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
|
||||
(Distance::feet(10.0), "typical"),
|
||||
(Distance::feet(12.0), "highway"),
|
||||
];
|
||||
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
|
||||
choices.swap(1, 0);
|
||||
}
|
||||
choices
|
||||
}
|
||||
// https://www.gov.uk/government/publications/cycle-infrastructure-design-ltn-120 table
|
||||
// 5-2
|
||||
LaneType::Biking => vec![
|
||||
(Distance::meters(2.0), "standard"),
|
||||
(Distance::meters(1.5), "absolute minimum"),
|
||||
],
|
||||
// https://nacto.org/publication/urban-street-design-guide/street-design-elements/transit-streets/dedicated-curbside-offset-bus-lanes/
|
||||
LaneType::Bus => vec![
|
||||
(Distance::feet(12.0), "normal"),
|
||||
(Distance::feet(10.0), "minimum"),
|
||||
],
|
||||
// https://nacto.org/publication/urban-street-design-guide/street-design-elements/lane-width/
|
||||
LaneType::Parking => {
|
||||
let mut choices = vec![
|
||||
(Distance::feet(7.0), "narrow"),
|
||||
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
|
||||
(Distance::feet(9.0), "wide"),
|
||||
(Distance::feet(15.0), "loading zone"),
|
||||
];
|
||||
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
|
||||
choices.swap(1, 0);
|
||||
}
|
||||
choices
|
||||
}
|
||||
// Just a guess
|
||||
LaneType::SharedLeftTurn => vec![(NORMAL_LANE_THICKNESS, "default")],
|
||||
// These're often converted from existing lanes, so just retain that width
|
||||
LaneType::Construction => vec![(NORMAL_LANE_THICKNESS, "default")],
|
||||
// No idea, just using this for now...
|
||||
LaneType::LightRail => vec![(NORMAL_LANE_THICKNESS, "default")],
|
||||
// http://www.seattle.gov/rowmanual/manual/4_11.asp
|
||||
LaneType::Sidewalk => vec![
|
||||
(SIDEWALK_THICKNESS, "default"),
|
||||
(Distance::feet(6.0), "wide"),
|
||||
],
|
||||
LaneType::Shoulder => vec![(SHOULDER_THICKNESS, "default")],
|
||||
// Pretty wild guesses
|
||||
LaneType::Buffer(BufferType::Stripes) => vec![(Distance::meters(1.5), "default")],
|
||||
LaneType::Buffer(BufferType::FlexPosts) => {
|
||||
vec![(Distance::meters(1.5), "default")]
|
||||
}
|
||||
LaneType::Buffer(BufferType::Planters) => {
|
||||
vec![(Distance::meters(2.0), "default")]
|
||||
}
|
||||
LaneType::Buffer(BufferType::JerseyBarrier) => {
|
||||
vec![(Distance::meters(1.5), "default")]
|
||||
}
|
||||
LaneType::Buffer(BufferType::Curb) => vec![(Distance::meters(0.5), "default")],
|
||||
}
|
||||
}
|
||||
|
||||
/// Put a list of forward and backward lanes into left-to-right order, depending on the driving
|
||||
/// side. Both input lists should be ordered from the center of the road going outwards.
|
||||
pub(crate) fn assemble_ltr(
|
||||
mut fwd_side: Vec<LaneSpec>,
|
||||
mut back_side: Vec<LaneSpec>,
|
||||
driving_side: DrivingSide,
|
||||
) -> Vec<LaneSpec> {
|
||||
match driving_side {
|
||||
DrivingSide::Right => {
|
||||
back_side.reverse();
|
||||
back_side.extend(fwd_side);
|
||||
back_side
|
||||
}
|
||||
DrivingSide::Left => {
|
||||
fwd_side.reverse();
|
||||
fwd_side.extend(back_side);
|
||||
fwd_side
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// None if bidirectional. If it's one-way, which direction is that relative to the road?
|
||||
/// (Usually forwards)
|
||||
pub fn oneway_for_driving(lanes: &[LaneSpec]) -> Option<Direction> {
|
||||
let mut fwd = false;
|
||||
let mut back = false;
|
||||
for x in lanes {
|
||||
if x.lt == LaneType::Driving {
|
||||
if x.dir == Direction::Fwd {
|
||||
fwd = true;
|
||||
} else {
|
||||
back = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if fwd && back {
|
||||
// Bidirectional
|
||||
None
|
||||
} else if fwd {
|
||||
Some(Direction::Fwd)
|
||||
} else if back {
|
||||
Some(Direction::Back)
|
||||
} else {
|
||||
// Not driveable at all
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
street_network/Cargo.toml
Normal file
18
street_network/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "street_network"
|
||||
version = "0.1.0"
|
||||
authors = ["Dustin Carlino <dabreegster@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aabb-quadtree = "0.1.0"
|
||||
abstio = { path = "../abstio" }
|
||||
abstutil = { path = "../abstutil" }
|
||||
anyhow = "1.0.38"
|
||||
geojson = { version = "0.22.2", features = ["geo-types"] }
|
||||
geom = { path = "../geom" }
|
||||
kml = { path = "../kml" }
|
||||
log = "0.4.14"
|
||||
petgraph = { version = "0.6.0" }
|
||||
serde = "1.0.123"
|
||||
serde_json = "1.0.61"
|
@ -3,9 +3,9 @@ use geojson::Feature;
|
||||
|
||||
use abstutil::Timer;
|
||||
|
||||
use crate::RawMap;
|
||||
use crate::StreetNetwork;
|
||||
|
||||
impl RawMap {
|
||||
impl StreetNetwork {
|
||||
/// Assumes `run_all_simplifications` has been called if desired
|
||||
pub fn save_to_geojson(&self, output_path: String, timer: &mut Timer) -> Result<()> {
|
||||
// TODO InitialMap is going away very soon, but we still need it
|
@ -1,4 +1,4 @@
|
||||
//! Naming is confusing, but RawMap -> InitialMap -> Map. InitialMap is separate pretty much just
|
||||
//! Naming is confusing, but StreetNetwork -> InitialMap -> Map. InitialMap is separate pretty much just
|
||||
//! for the step of producing <https://a-b-street.github.io/docs/tech/map/importing/geometry.html>.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
@ -6,7 +6,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use abstutil::{Tags, Timer};
|
||||
use geom::{Bounds, Circle, Distance, PolyLine, Polygon, Pt2D};
|
||||
|
||||
use crate::{osm, InputRoad, IntersectionType, OriginalRoad, RawMap};
|
||||
use crate::{osm, InputRoad, IntersectionType, OriginalRoad, StreetNetwork};
|
||||
|
||||
pub struct InitialMap {
|
||||
pub roads: BTreeMap<OriginalRoad, Road>,
|
||||
@ -28,7 +28,7 @@ pub struct Road {
|
||||
}
|
||||
|
||||
impl Road {
|
||||
pub fn new(map: &RawMap, id: OriginalRoad) -> Road {
|
||||
pub fn new(map: &StreetNetwork, id: OriginalRoad) -> Road {
|
||||
let road = &map.roads[&id];
|
||||
let (trimmed_center_pts, total_width) = road.untrimmed_road_geometry();
|
||||
|
||||
@ -62,7 +62,7 @@ pub struct Intersection {
|
||||
}
|
||||
|
||||
impl InitialMap {
|
||||
pub fn new(raw: &RawMap, bounds: &Bounds, timer: &mut Timer) -> InitialMap {
|
||||
pub fn new(raw: &StreetNetwork, bounds: &Bounds, timer: &mut Timer) -> InitialMap {
|
||||
let mut m = InitialMap {
|
||||
roads: BTreeMap::new(),
|
||||
intersections: BTreeMap::new(),
|
508
street_network/src/lib.rs
Normal file
508
street_network/src/lib.rs
Normal file
@ -0,0 +1,508 @@
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use petgraph::graphmap::DiGraphMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
|
||||
use geom::{Angle, Distance, GPSBounds, PolyLine, Polygon, Pt2D};
|
||||
|
||||
pub use self::geometry::{intersection_polygon, InputRoad};
|
||||
pub use self::lane_specs::get_lane_specs_ltr;
|
||||
pub use self::types::{
|
||||
BufferType, Direction, DrivingSide, IntersectionType, LaneSpec, LaneType, MapConfig,
|
||||
NamePerLanguage, NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS,
|
||||
};
|
||||
|
||||
mod edit;
|
||||
mod export;
|
||||
pub mod geometry;
|
||||
pub mod initial;
|
||||
mod lane_specs;
|
||||
pub mod osm;
|
||||
mod transform;
|
||||
mod types;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct StreetNetwork {
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub roads: BTreeMap<OriginalRoad, RawRoad>,
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub intersections: BTreeMap<osm::NodeID, RawIntersection>,
|
||||
|
||||
pub boundary_polygon: Polygon,
|
||||
pub gps_bounds: GPSBounds,
|
||||
pub config: MapConfig,
|
||||
}
|
||||
|
||||
/// A way to refer to roads across many maps and over time. Also trivial to relate with OSM to find
|
||||
/// upstream problems.
|
||||
//
|
||||
// - Using LonLat is more indirect, and f64's need to be trimmed and compared carefully with epsilon
|
||||
// checks.
|
||||
// - TODO Look at some stable ID standard like linear referencing
|
||||
// (https://github.com/opentraffic/architecture/issues/1).
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct OriginalRoad {
|
||||
pub osm_way_id: osm::WayID,
|
||||
pub i1: osm::NodeID,
|
||||
pub i2: osm::NodeID,
|
||||
}
|
||||
|
||||
impl fmt::Display for OriginalRoad {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"OriginalRoad({} from {} to {}",
|
||||
self.osm_way_id, self.i1, self.i2
|
||||
)
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for OriginalRoad {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginalRoad {
|
||||
pub fn new(way: i64, (i1, i2): (i64, i64)) -> OriginalRoad {
|
||||
OriginalRoad {
|
||||
osm_way_id: osm::WayID(way),
|
||||
i1: osm::NodeID(i1),
|
||||
i2: osm::NodeID(i2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the OriginalRoad in a way that can be copied to Rust code.
|
||||
pub fn as_string_code(&self) -> String {
|
||||
format!(
|
||||
"OriginalRoad::new({}, ({}, {}))",
|
||||
self.osm_way_id.0, self.i1.0, self.i2.0
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_common_endpoint(&self, other: OriginalRoad) -> bool {
|
||||
if self.i1 == other.i1 || self.i1 == other.i2 {
|
||||
return true;
|
||||
}
|
||||
if self.i2 == other.i1 || self.i2 == other.i2 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// TODO Doesn't handle two roads between the same pair of intersections
|
||||
pub fn common_endpt(&self, other: OriginalRoad) -> osm::NodeID {
|
||||
#![allow(clippy::suspicious_operation_groupings)]
|
||||
if self.i1 == other.i1 || self.i1 == other.i2 {
|
||||
return self.i1;
|
||||
}
|
||||
if self.i2 == other.i1 || self.i2 == other.i2 {
|
||||
return self.i2;
|
||||
}
|
||||
panic!("{:?} and {:?} have no common_endpt", self, other);
|
||||
}
|
||||
}
|
||||
|
||||
impl StreetNetwork {
|
||||
pub fn blank() -> Self {
|
||||
Self {
|
||||
roads: BTreeMap::new(),
|
||||
intersections: BTreeMap::new(),
|
||||
// Some nonsense thing
|
||||
boundary_polygon: Polygon::rectangle(1.0, 1.0),
|
||||
gps_bounds: GPSBounds::new(),
|
||||
config: MapConfig {
|
||||
driving_side: DrivingSide::Right,
|
||||
bikes_can_use_bus_lanes: true,
|
||||
inferred_sidewalks: true,
|
||||
street_parking_spot_length: Distance::meters(8.0),
|
||||
turn_on_red: true,
|
||||
find_dog_legs_experiment: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Might be better to maintain this instead of doing a search everytime.
|
||||
pub fn roads_per_intersection(&self, i: osm::NodeID) -> Vec<OriginalRoad> {
|
||||
let mut results = Vec::new();
|
||||
for id in self.roads.keys() {
|
||||
if id.i1 == i || id.i2 == i {
|
||||
results.push(*id);
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
pub fn new_osm_node_id(&self, start: i64) -> osm::NodeID {
|
||||
assert!(start < 0);
|
||||
// Slow, but deterministic.
|
||||
let mut osm_node_id = start;
|
||||
loop {
|
||||
if self.intersections.keys().any(|i| i.0 == osm_node_id) {
|
||||
osm_node_id -= 1;
|
||||
} else {
|
||||
return osm::NodeID(osm_node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// (Intersection polygon, polygons for roads, list of labeled polygons to debug)
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn preview_intersection(
|
||||
&self,
|
||||
id: osm::NodeID,
|
||||
) -> Result<(Polygon, Vec<Polygon>, Vec<(String, Polygon)>)> {
|
||||
let mut input_roads = Vec::new();
|
||||
for r in self.roads_per_intersection(id) {
|
||||
input_roads.push(initial::Road::new(self, r).to_input_road());
|
||||
}
|
||||
let results = intersection_polygon(
|
||||
id,
|
||||
input_roads,
|
||||
// This'll be empty unless we've called merge_short_road
|
||||
&self.intersections[&id].trim_roads_for_merging,
|
||||
)?;
|
||||
Ok((
|
||||
results.intersection_polygon,
|
||||
results
|
||||
.trimmed_center_pts
|
||||
.into_values()
|
||||
.map(|(pl, half_width)| pl.make_polygons(2.0 * half_width))
|
||||
.collect(),
|
||||
results.debug,
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate the trimmed `PolyLine` for a single RawRoad by calculating both intersections
|
||||
pub fn trimmed_road_geometry(&self, road_id: OriginalRoad) -> Result<PolyLine> {
|
||||
// First trim at one of the endpoints
|
||||
let trimmed_center_pts = {
|
||||
let mut input_roads = Vec::new();
|
||||
for r in self.roads_per_intersection(road_id.i1) {
|
||||
input_roads.push(initial::Road::new(self, r).to_input_road());
|
||||
}
|
||||
let mut results = intersection_polygon(
|
||||
road_id.i1,
|
||||
input_roads,
|
||||
// TODO Not sure if we should use this or not
|
||||
&BTreeMap::new(),
|
||||
)?;
|
||||
results.trimmed_center_pts.remove(&road_id).unwrap().0
|
||||
};
|
||||
|
||||
// Now the second
|
||||
{
|
||||
let mut input_roads = Vec::new();
|
||||
for r in self.roads_per_intersection(road_id.i2) {
|
||||
let mut road = initial::Road::new(self, r).to_input_road();
|
||||
if r == road_id {
|
||||
road.center_pts = trimmed_center_pts.clone();
|
||||
}
|
||||
input_roads.push(road);
|
||||
}
|
||||
let mut results = intersection_polygon(
|
||||
road_id.i2,
|
||||
input_roads,
|
||||
// TODO Not sure if we should use this or not
|
||||
&BTreeMap::new(),
|
||||
)?;
|
||||
Ok(results.trimmed_center_pts.remove(&road_id).unwrap().0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mutations and supporting queries
|
||||
impl StreetNetwork {
|
||||
pub fn can_delete_intersection(&self, i: osm::NodeID) -> bool {
|
||||
self.roads_per_intersection(i).is_empty()
|
||||
}
|
||||
|
||||
pub fn delete_intersection(&mut self, id: osm::NodeID) {
|
||||
if !self.can_delete_intersection(id) {
|
||||
panic!(
|
||||
"Can't delete_intersection {}, must have roads connected",
|
||||
id
|
||||
);
|
||||
}
|
||||
self.intersections.remove(&id).unwrap();
|
||||
}
|
||||
|
||||
pub fn move_intersection(&mut self, id: osm::NodeID, point: Pt2D) -> Option<Vec<OriginalRoad>> {
|
||||
self.intersections.get_mut(&id).unwrap().point = point;
|
||||
|
||||
// Update all the roads.
|
||||
let mut fixed = Vec::new();
|
||||
for r in self.roads_per_intersection(id) {
|
||||
fixed.push(r);
|
||||
let road = self.roads.get_mut(&r).unwrap();
|
||||
if r.i1 == id {
|
||||
road.osm_center_points[0] = point;
|
||||
} else {
|
||||
assert_eq!(r.i2, id);
|
||||
*road.osm_center_points.last_mut().unwrap() = point;
|
||||
}
|
||||
}
|
||||
|
||||
Some(fixed)
|
||||
}
|
||||
|
||||
pub fn closest_intersection(&self, pt: Pt2D) -> osm::NodeID {
|
||||
self.intersections
|
||||
.iter()
|
||||
.min_by_key(|(_, i)| i.point.dist_to(pt))
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn path_dist_to(&self, from: osm::NodeID, to: osm::NodeID) -> Option<Distance> {
|
||||
let mut graph = DiGraphMap::new();
|
||||
for (id, r) in &self.roads {
|
||||
graph.add_edge(id.i1, id.i2, id);
|
||||
if r.oneway_for_driving().is_none() {
|
||||
graph.add_edge(id.i2, id.i1, id);
|
||||
}
|
||||
}
|
||||
petgraph::algo::dijkstra(&graph, from, Some(to), |(_, _, r)| {
|
||||
// TODO Expensive!
|
||||
self.roads[r].length()
|
||||
})
|
||||
.get(&to)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RawRoad {
|
||||
/// This is effectively a PolyLine, except there's a case where we need to plumb forward
|
||||
/// cul-de-sac roads for roundabout handling. No transformation of these points whatsoever has
|
||||
/// happened.
|
||||
pub osm_center_points: Vec<Pt2D>,
|
||||
pub osm_tags: Tags,
|
||||
pub turn_restrictions: Vec<(RestrictionType, OriginalRoad)>,
|
||||
/// (via, to). For turn restrictions where 'via' is an entire road. Only BanTurns.
|
||||
pub complicated_turn_restrictions: Vec<(OriginalRoad, OriginalRoad)>,
|
||||
pub percent_incline: f64,
|
||||
/// Is there a tagged crosswalk near each end of the road?
|
||||
pub crosswalk_forward: bool,
|
||||
pub crosswalk_backward: bool,
|
||||
/// Barrier nodes along this road's original center line.
|
||||
// TODO Preserving these across transformations (especially merging dual carriageways!) could
|
||||
// be really hard. It might be better to split the road into two pieces to match the more often
|
||||
// used OSM style.
|
||||
pub barrier_nodes: Vec<Pt2D>,
|
||||
|
||||
/// Derived from osm_tags. Not automatically updated.
|
||||
pub lane_specs_ltr: Vec<LaneSpec>,
|
||||
}
|
||||
|
||||
impl RawRoad {
|
||||
pub fn new(osm_center_points: Vec<Pt2D>, osm_tags: Tags, config: &MapConfig) -> Result<Self> {
|
||||
// Just flush out errors immediately.
|
||||
// TODO Store the PolyLine, not a Vec<Pt2D>
|
||||
let _ = PolyLine::new(osm_center_points.clone())?;
|
||||
|
||||
let lane_specs_ltr = get_lane_specs_ltr(&osm_tags, config);
|
||||
|
||||
Ok(Self {
|
||||
osm_center_points,
|
||||
osm_tags,
|
||||
turn_restrictions: Vec::new(),
|
||||
complicated_turn_restrictions: Vec::new(),
|
||||
percent_incline: 0.0,
|
||||
// Start assuming there's a crosswalk everywhere, and maybe filter it down
|
||||
// later
|
||||
crosswalk_forward: true,
|
||||
crosswalk_backward: true,
|
||||
barrier_nodes: Vec::new(),
|
||||
|
||||
lane_specs_ltr,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO For the moment, treating all rail things as light rail
|
||||
pub fn is_light_rail(&self) -> bool {
|
||||
self.osm_tags.is_any("railway", vec!["light_rail", "rail"])
|
||||
}
|
||||
|
||||
pub fn is_footway(&self) -> bool {
|
||||
self.osm_tags.is_any(
|
||||
osm::HIGHWAY,
|
||||
vec![
|
||||
"cycleway",
|
||||
"footway",
|
||||
"path",
|
||||
"pedestrian",
|
||||
"steps",
|
||||
"track",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_service(&self) -> bool {
|
||||
self.osm_tags.is(osm::HIGHWAY, "service")
|
||||
}
|
||||
|
||||
pub fn is_cycleway(&self) -> bool {
|
||||
// Don't repeat the logic looking at the tags, just see what lanes we'll create
|
||||
let mut bike = false;
|
||||
for spec in &self.lane_specs_ltr {
|
||||
if spec.lt == LaneType::Biking {
|
||||
bike = true;
|
||||
} else if spec.lt != LaneType::Shoulder {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bike
|
||||
}
|
||||
|
||||
pub fn is_driveable(&self) -> bool {
|
||||
self.lane_specs_ltr
|
||||
.iter()
|
||||
.any(|spec| spec.lt == LaneType::Driving)
|
||||
}
|
||||
|
||||
pub fn oneway_for_driving(&self) -> Option<Direction> {
|
||||
LaneSpec::oneway_for_driving(&self.lane_specs_ltr)
|
||||
}
|
||||
|
||||
/// Points from first to last point. Undefined for loops.
|
||||
pub fn angle(&self) -> Angle {
|
||||
self.osm_center_points[0].angle_to(*self.osm_center_points.last().unwrap())
|
||||
}
|
||||
|
||||
pub fn length(&self) -> Distance {
|
||||
PolyLine::unchecked_new(self.osm_center_points.clone()).length()
|
||||
}
|
||||
|
||||
pub fn get_zorder(&self) -> isize {
|
||||
if let Some(layer) = self.osm_tags.get("layer") {
|
||||
match layer.parse::<f64>() {
|
||||
// Just drop .5 for now
|
||||
Ok(l) => l as isize,
|
||||
Err(_) => {
|
||||
warn!("Weird layer={} on {}", layer, self.osm_url());
|
||||
0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the corrected (but untrimmed) center and total width for a road
|
||||
pub fn untrimmed_road_geometry(&self) -> (PolyLine, Distance) {
|
||||
let mut total_width = Distance::ZERO;
|
||||
let mut sidewalk_right = None;
|
||||
let mut sidewalk_left = None;
|
||||
for l in &self.lane_specs_ltr {
|
||||
total_width += l.width;
|
||||
if l.lt.is_walkable() {
|
||||
if l.dir == Direction::Back {
|
||||
sidewalk_left = Some(l.width);
|
||||
} else {
|
||||
sidewalk_right = Some(l.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a sidewalk on only one side, adjust the true center of the road.
|
||||
// TODO I don't remember the rationale for doing this in the first place. What if there's a
|
||||
// shoulder and a sidewalk of different widths? We don't do anything then
|
||||
let mut true_center = match PolyLine::new(self.osm_center_points.clone()) {
|
||||
Ok(pl) => pl,
|
||||
Err(err) => panic!(
|
||||
"untrimmed_road_geometry of {} failed: {}",
|
||||
self.osm_url(),
|
||||
err
|
||||
),
|
||||
};
|
||||
match (sidewalk_right, sidewalk_left) {
|
||||
(Some(w), None) => {
|
||||
true_center = true_center.must_shift_right(w / 2.0);
|
||||
}
|
||||
(None, Some(w)) => {
|
||||
true_center = true_center.must_shift_right(w / 2.0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
(true_center, total_width)
|
||||
}
|
||||
|
||||
pub fn osm_url(&self) -> String {
|
||||
// Since we don't store an OriginalRoad (since we may need to update it during
|
||||
// transformations), this may be convenient
|
||||
format!(
|
||||
"http://openstreetmap.org/way/{}",
|
||||
self.osm_tags.get(osm::OSM_WAY_ID).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RawIntersection {
|
||||
/// Represents the original place where OSM center-lines meet. This may be meaningless beyond
|
||||
/// StreetNetwork; roads and intersections get merged and deleted.
|
||||
pub point: Pt2D,
|
||||
pub intersection_type: IntersectionType,
|
||||
pub elevation: Distance,
|
||||
|
||||
// true if src_i matches this intersection (or the deleted/consolidated one, whatever)
|
||||
pub trim_roads_for_merging: BTreeMap<(osm::WayID, bool), Pt2D>,
|
||||
}
|
||||
|
||||
impl RawIntersection {
|
||||
pub fn new(point: Pt2D, intersection_type: IntersectionType) -> Self {
|
||||
Self {
|
||||
point,
|
||||
intersection_type,
|
||||
// Filled out later
|
||||
elevation: Distance::ZERO,
|
||||
trim_roads_for_merging: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_border(&self) -> bool {
|
||||
self.intersection_type == IntersectionType::Border
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum RestrictionType {
|
||||
BanTurns,
|
||||
OnlyAllowTurns,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TurnRestriction(pub OriginalRoad, pub RestrictionType, pub OriginalRoad);
|
||||
|
||||
impl RestrictionType {
|
||||
pub fn new(restriction: &str) -> Option<RestrictionType> {
|
||||
// TODO There's a huge space of things not represented yet: time conditions, bus-only, no
|
||||
// right turn on red...
|
||||
|
||||
// There are so many possibilities:
|
||||
// https://taginfo.openstreetmap.org/keys/restriction#values
|
||||
// Just attempt to bucket into allow / deny.
|
||||
if restriction.contains("no_") || restriction == "psv" {
|
||||
Some(RestrictionType::BanTurns)
|
||||
} else if restriction.contains("only_") {
|
||||
Some(RestrictionType::OnlyAllowTurns)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,12 @@ use anyhow::Result;
|
||||
use geom::{Distance, Pt2D};
|
||||
|
||||
use crate::osm::NodeID;
|
||||
use crate::{osm, IntersectionType, OriginalRoad, RawMap};
|
||||
use crate::{osm, IntersectionType, OriginalRoad, StreetNetwork};
|
||||
|
||||
/// Collapse degenerate intersections:
|
||||
/// - between two cycleways
|
||||
/// - when the lane specs match and only "unimportant" OSM tags differ
|
||||
pub fn collapse(raw: &mut RawMap) {
|
||||
pub fn collapse(raw: &mut StreetNetwork) {
|
||||
let mut merge: Vec<NodeID> = Vec::new();
|
||||
for id in raw.intersections.keys() {
|
||||
let roads = raw.roads_per_intersection(*id);
|
||||
@ -35,7 +35,7 @@ pub fn collapse(raw: &mut RawMap) {
|
||||
// Results look good so far.
|
||||
}
|
||||
|
||||
fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &RawMap) -> Result<()> {
|
||||
fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &StreetNetwork) -> Result<()> {
|
||||
let road1 = &raw.roads[&r1];
|
||||
let road2 = &raw.roads[&r2];
|
||||
|
||||
@ -135,7 +135,7 @@ fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &RawMap) -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn collapse_intersection(raw: &mut RawMap, i: NodeID) {
|
||||
pub fn collapse_intersection(raw: &mut StreetNetwork, i: NodeID) {
|
||||
let roads = raw.roads_per_intersection(i);
|
||||
assert_eq!(roads.len(), 2);
|
||||
let mut r1 = roads[0];
|
||||
@ -226,7 +226,7 @@ const SHORT_THRESHOLD: Distance = Distance::const_meters(30.0);
|
||||
/// "stubs." Trim those.
|
||||
///
|
||||
/// Also do the same thing for extremely short dead-end service roads.
|
||||
pub fn trim_deadends(raw: &mut RawMap) {
|
||||
pub fn trim_deadends(raw: &mut StreetNetwork) {
|
||||
let mut remove_roads = BTreeSet::new();
|
||||
let mut remove_intersections = BTreeSet::new();
|
||||
for (id, i) in &raw.intersections {
|
@ -1,12 +1,12 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::{osm, OriginalRoad, RawMap};
|
||||
use crate::{osm, OriginalRoad, StreetNetwork};
|
||||
|
||||
/// Does this road go between two divided one-ways? Ideally they're tagged explicitly
|
||||
/// (https://wiki.openstreetmap.org/wiki/Tag:dual_carriageway%3Dyes), but we can also apply simple
|
||||
/// heuristics to guess this.
|
||||
#[allow(unused)]
|
||||
pub fn connects_dual_carriageway(map: &RawMap, id: &OriginalRoad) -> bool {
|
||||
pub fn connects_dual_carriageway(map: &StreetNetwork, id: &OriginalRoad) -> bool {
|
||||
let connectors_angle = map.roads[id].angle();
|
||||
// There are false positives like https://www.openstreetmap.org/way/4636259 when we're looking
|
||||
// at a segment along a marked dual carriageway. Filter out by requiring the intersecting dual
|
@ -1,8 +1,7 @@
|
||||
use abstio::MapName;
|
||||
use abstutil::Timer;
|
||||
use geom::Distance;
|
||||
|
||||
use crate::{osm, IntersectionType, OriginalRoad, RawMap};
|
||||
use crate::{osm, IntersectionType, OriginalRoad, StreetNetwork};
|
||||
|
||||
/// Combines a few different sources/methods to decide which roads are short. Marks them for
|
||||
/// merging.
|
||||
@ -10,7 +9,7 @@ use crate::{osm, IntersectionType, OriginalRoad, RawMap};
|
||||
/// 1) Anything tagged in OSM
|
||||
/// 2) Anything a temporary local merge_osm_ways.json file
|
||||
/// 3) If `consolidate_all` is true, an experimental distance-based heuristic
|
||||
pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<OriginalRoad> {
|
||||
pub fn find_short_roads(map: &mut StreetNetwork, consolidate_all: bool) -> Vec<OriginalRoad> {
|
||||
let mut roads = Vec::new();
|
||||
for (id, road) in &map.roads {
|
||||
if road.osm_tags.is("junction", "intersection") {
|
||||
@ -24,21 +23,7 @@ pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<Original
|
||||
}
|
||||
|
||||
// Gradually rolling out
|
||||
if vec![
|
||||
MapName::seattle("montlake"),
|
||||
MapName::seattle("downtown"),
|
||||
MapName::seattle("lakeslice"),
|
||||
MapName::new("us", "phoenix", "tempe"),
|
||||
MapName::new("gb", "bristol", "east"),
|
||||
//MapName::new("gb", "leeds", "north"),
|
||||
//MapName::new("gb", "london", "camden"),
|
||||
MapName::new("gb", "london", "kennington"),
|
||||
//MapName::new("gb", "london", "southwark"),
|
||||
//MapName::new("gb", "manchester", "levenshulme"),
|
||||
MapName::new("pl", "krakow", "center"),
|
||||
]
|
||||
.contains(&map.name)
|
||||
{
|
||||
if map.config.find_dog_legs_experiment {
|
||||
roads.extend(map.find_dog_legs());
|
||||
}
|
||||
|
||||
@ -54,7 +39,7 @@ pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<Original
|
||||
map.mark_short_roads(roads)
|
||||
}
|
||||
|
||||
fn distance_heuristic(id: OriginalRoad, map: &RawMap) -> bool {
|
||||
fn distance_heuristic(id: OriginalRoad, map: &StreetNetwork) -> bool {
|
||||
let road_length = if let Ok(pl) = map.trimmed_road_geometry(id) {
|
||||
pl.length()
|
||||
} else {
|
||||
@ -67,7 +52,7 @@ fn distance_heuristic(id: OriginalRoad, map: &RawMap) -> bool {
|
||||
road_length < Distance::meters(5.0)
|
||||
}
|
||||
|
||||
impl RawMap {
|
||||
impl StreetNetwork {
|
||||
fn mark_short_roads(&mut self, list: Vec<OriginalRoad>) -> Vec<OriginalRoad> {
|
||||
for id in &list {
|
||||
self.roads
|
||||
@ -172,7 +157,7 @@ impl RawMap {
|
||||
}
|
||||
|
||||
// TODO Dedupe with find_divided_highways logic in parking_mapper
|
||||
fn dual_carriageway_split(map: &RawMap, roads: Vec<OriginalRoad>) -> bool {
|
||||
fn dual_carriageway_split(map: &StreetNetwork, roads: Vec<OriginalRoad>) -> bool {
|
||||
assert_eq!(roads.len(), 3);
|
||||
// Look for one-way roads with the same name
|
||||
for (r1, r2) in [
|
@ -2,13 +2,13 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{osm, IntersectionType, OriginalRoad, RawMap, RestrictionType};
|
||||
use crate::{osm, IntersectionType, OriginalRoad, RestrictionType, StreetNetwork};
|
||||
|
||||
// TODO After merging a road, trying to drag the surviving intersection in map_editor crashes. I
|
||||
// bet the underlying problem there would help debug automated transformations near merged roads
|
||||
// too.
|
||||
|
||||
impl RawMap {
|
||||
impl StreetNetwork {
|
||||
/// Returns (the surviving intersection, the deleted intersection, deleted roads, new roads)
|
||||
pub fn merge_short_road(
|
||||
&mut self,
|
||||
@ -192,7 +192,7 @@ impl RawMap {
|
||||
}
|
||||
|
||||
/// Merge all roads marked with `junction=intersection`
|
||||
pub fn merge_all_junctions(map: &mut RawMap) {
|
||||
pub fn merge_all_junctions(map: &mut StreetNetwork) {
|
||||
let mut queue: VecDeque<OriginalRoad> = VecDeque::new();
|
||||
for (id, road) in &map.roads {
|
||||
if road.osm_tags.is("junction", "intersection") {
|
@ -1,6 +1,6 @@
|
||||
use abstutil::Timer;
|
||||
|
||||
use crate::RawMap;
|
||||
use crate::StreetNetwork;
|
||||
|
||||
mod collapse_intersections;
|
||||
mod dual_carriageways;
|
||||
@ -10,20 +10,20 @@ mod remove_disconnected;
|
||||
mod shrink_roads;
|
||||
mod snappy;
|
||||
|
||||
impl RawMap {
|
||||
impl StreetNetwork {
|
||||
// TODO I suspect we'll soon take a full struct of options, maybe even a list of transformation
|
||||
// enums to run in order
|
||||
/// Run a sequence of transformations to the RawMap before converting it to a full Map.
|
||||
/// Run a sequence of transformations to the StreetNetwork before converting it to a full Map.
|
||||
///
|
||||
/// We don't want to run these during the OSM->RawMap import stage, because we want to use the
|
||||
/// map_editor tool to debug the RawMap.
|
||||
/// We don't want to run these during the OSM->StreetNetwork import stage, because we want to use the
|
||||
/// map_editor tool to debug the StreetNetwork.
|
||||
pub fn run_all_simplifications(
|
||||
&mut self,
|
||||
consolidate_all_intersections: bool,
|
||||
remove_disconnected: bool,
|
||||
timer: &mut Timer,
|
||||
) {
|
||||
timer.start("simplify RawMap");
|
||||
timer.start("simplify StreetNetwork");
|
||||
|
||||
timer.start("trimming dead-end cycleways (round 1)");
|
||||
collapse_intersections::trim_deadends(self);
|
||||
@ -56,6 +56,6 @@ impl RawMap {
|
||||
shrink_roads::shrink(self, timer);
|
||||
timer.stop("shrinking overlapping roads");
|
||||
|
||||
timer.stop("simplify RawMap");
|
||||
timer.stop("simplify StreetNetwork");
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@ use std::collections::BTreeSet;
|
||||
|
||||
use abstutil::{MultiMap, Timer};
|
||||
|
||||
use crate::{osm, OriginalRoad, RawMap};
|
||||
use crate::{osm, OriginalRoad, StreetNetwork};
|
||||
|
||||
/// Some roads might be totally disconnected from the largest clump because of how the map's
|
||||
/// bounding polygon was drawn, or bad map data, or which roads are filtered from OSM. Remove them.
|
||||
pub fn remove_disconnected_roads(map: &mut RawMap, timer: &mut Timer) {
|
||||
pub fn remove_disconnected_roads(map: &mut StreetNetwork, timer: &mut Timer) {
|
||||
timer.start("removing disconnected roads");
|
||||
// This is a simple floodfill, not Tarjan's. Assumes all roads bidirectional.
|
||||
// All the usizes are indices into the original list of roads
|
@ -3,11 +3,11 @@ use std::collections::{HashMap, HashSet};
|
||||
use aabb_quadtree::QuadTree;
|
||||
use abstutil::Timer;
|
||||
|
||||
use crate::{osm, RawMap};
|
||||
use crate::{osm, StreetNetwork};
|
||||
|
||||
/// Look for roads that physically overlap, but aren't connected by an intersection. Shrink their
|
||||
/// width.
|
||||
pub fn shrink(raw: &mut RawMap, timer: &mut Timer) {
|
||||
pub fn shrink(raw: &mut StreetNetwork, timer: &mut Timer) {
|
||||
let mut road_centers = HashMap::new();
|
||||
let mut road_polygons = HashMap::new();
|
||||
let mut overlapping = Vec::new();
|
@ -4,12 +4,12 @@ use abstutil::MultiMap;
|
||||
use geom::{Distance, FindClosest, Line, PolyLine};
|
||||
use kml::{ExtraShape, ExtraShapes};
|
||||
|
||||
use crate::{Direction, OriginalRoad, RawMap};
|
||||
use crate::{Direction, OriginalRoad, StreetNetwork};
|
||||
|
||||
const DEBUG_OUTPUT: bool = true;
|
||||
|
||||
/// Snap separately mapped cycleways to main roads.
|
||||
pub fn snap_cycleways(map: &mut RawMap) {
|
||||
pub fn snap_cycleways(map: &mut StreetNetwork) {
|
||||
if true {
|
||||
return;
|
||||
}
|
||||
@ -123,7 +123,7 @@ struct Cycleway {
|
||||
// cycleways hit.
|
||||
// TODO Or look for cycleway polygons strictly overlapping thick road polygons
|
||||
fn v1(
|
||||
map: &RawMap,
|
||||
map: &StreetNetwork,
|
||||
cycleways: &[Cycleway],
|
||||
road_edges: &HashMap<(OriginalRoad, Direction), PolyLine>,
|
||||
) -> MultiMap<OriginalRoad, (OriginalRoad, Direction)> {
|
||||
@ -220,16 +220,19 @@ fn v1(
|
||||
}
|
||||
}
|
||||
|
||||
if DEBUG_OUTPUT {
|
||||
// TODO If this is still useful, figure out how to wire it up
|
||||
let _debug_shapes = ExtraShapes {
|
||||
shapes: debug_shapes,
|
||||
};
|
||||
|
||||
/*if DEBUG_OUTPUT {
|
||||
abstio::write_binary(
|
||||
map.name
|
||||
.city
|
||||
.input_path(format!("{}_snapping.bin", map.name.map)),
|
||||
&ExtraShapes {
|
||||
shapes: debug_shapes,
|
||||
},
|
||||
&debug_shapes
|
||||
);
|
||||
}
|
||||
}*/
|
||||
|
||||
matches
|
||||
}
|
399
street_network/src/types.rs
Normal file
399
street_network/src/types.rs
Normal file
@ -0,0 +1,399 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
|
||||
use geom::Distance;
|
||||
|
||||
use crate::osm;
|
||||
|
||||
pub const NORMAL_LANE_THICKNESS: Distance = Distance::const_meters(2.5);
|
||||
const SERVICE_ROAD_LANE_THICKNESS: Distance = Distance::const_meters(1.5);
|
||||
pub const SIDEWALK_THICKNESS: Distance = Distance::const_meters(1.5);
|
||||
const SHOULDER_THICKNESS: Distance = Distance::const_meters(0.5);
|
||||
|
||||
/// None corresponds to the native name
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct NamePerLanguage(
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
pub(crate) BTreeMap<Option<String>, String>,
|
||||
);
|
||||
|
||||
impl NamePerLanguage {
|
||||
pub fn get(&self, lang: Option<&String>) -> &String {
|
||||
// TODO Can we avoid this clone?
|
||||
let lang = lang.cloned();
|
||||
if let Some(name) = self.0.get(&lang) {
|
||||
return name;
|
||||
}
|
||||
&self.0[&None]
|
||||
}
|
||||
|
||||
pub fn new(tags: &Tags) -> Option<NamePerLanguage> {
|
||||
let native_name = tags.get(osm::NAME)?;
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(None, native_name.to_string());
|
||||
for (k, v) in tags.inner() {
|
||||
if let Some(lang) = k.strip_prefix("name:") {
|
||||
map.insert(Some(lang.to_string()), v.to_string());
|
||||
}
|
||||
}
|
||||
Some(NamePerLanguage(map))
|
||||
}
|
||||
|
||||
pub fn unnamed() -> NamePerLanguage {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(None, "unnamed".to_string());
|
||||
NamePerLanguage(map)
|
||||
}
|
||||
|
||||
pub fn languages(&self) -> Vec<&String> {
|
||||
self.0.keys().flatten().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Direction {
|
||||
Fwd,
|
||||
Back,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn opposite(self) -> Direction {
|
||||
match self {
|
||||
Direction::Fwd => Direction::Back,
|
||||
Direction::Back => Direction::Fwd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Direction::Fwd => write!(f, "forwards"),
|
||||
Direction::Back => write!(f, "backwards"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MapConfig {
|
||||
/// If true, driving happens on the right side of the road (USA). If false, on the left
|
||||
/// (Australia).
|
||||
pub driving_side: DrivingSide,
|
||||
pub bikes_can_use_bus_lanes: bool,
|
||||
/// If true, roads without explicitly tagged sidewalks may have sidewalks or shoulders. If
|
||||
/// false, no sidewalks will be inferred if not tagged in OSM, and separate sidewalks will be
|
||||
/// included.
|
||||
pub inferred_sidewalks: bool,
|
||||
/// Street parking is divided into spots of this length. 8 meters is a reasonable default, but
|
||||
/// people in some regions might be more accustomed to squeezing into smaller spaces. This
|
||||
/// value can be smaller than the hardcoded maximum car length; cars may render on top of each
|
||||
/// other, but otherwise the simulation doesn't care.
|
||||
pub street_parking_spot_length: Distance,
|
||||
/// If true, turns on red which do not conflict crossing traffic ('right on red') are allowed
|
||||
pub turn_on_red: bool,
|
||||
|
||||
pub find_dog_legs_experiment: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
pub enum DrivingSide {
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum IntersectionType {
|
||||
StopSign,
|
||||
TrafficSignal,
|
||||
Border,
|
||||
Construction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum LaneType {
|
||||
Driving,
|
||||
Parking,
|
||||
Sidewalk,
|
||||
// Walkable like a Sidewalk, but very narrow. Used to model pedestrians walking on roads
|
||||
// without sidewalks.
|
||||
Shoulder,
|
||||
Biking,
|
||||
Bus,
|
||||
SharedLeftTurn,
|
||||
Construction,
|
||||
LightRail,
|
||||
Buffer(BufferType),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BufferType {
|
||||
/// Just paint!
|
||||
Stripes,
|
||||
/// Flex posts, wands, cones, other "weak" forms of protection. Can weave through them.
|
||||
FlexPosts,
|
||||
/// Sturdier planters, with gaps.
|
||||
Planters,
|
||||
/// Solid barrier, no gaps.
|
||||
JerseyBarrier,
|
||||
/// A raised curb
|
||||
Curb,
|
||||
}
|
||||
|
||||
impl LaneType {
|
||||
pub fn is_for_moving_vehicles(self) -> bool {
|
||||
match self {
|
||||
LaneType::Driving => true,
|
||||
LaneType::Biking => true,
|
||||
LaneType::Bus => true,
|
||||
LaneType::Parking => false,
|
||||
LaneType::Sidewalk => false,
|
||||
LaneType::Shoulder => false,
|
||||
LaneType::SharedLeftTurn => false,
|
||||
LaneType::Construction => false,
|
||||
LaneType::LightRail => true,
|
||||
LaneType::Buffer(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_any_movement(self) -> bool {
|
||||
match self {
|
||||
LaneType::Driving => true,
|
||||
LaneType::Biking => true,
|
||||
LaneType::Bus => true,
|
||||
LaneType::Parking => false,
|
||||
LaneType::Sidewalk => true,
|
||||
LaneType::Shoulder => true,
|
||||
LaneType::SharedLeftTurn => false,
|
||||
LaneType::Construction => false,
|
||||
LaneType::LightRail => true,
|
||||
LaneType::Buffer(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_walkable(self) -> bool {
|
||||
self == LaneType::Sidewalk || self == LaneType::Shoulder
|
||||
}
|
||||
|
||||
pub fn describe(self) -> &'static str {
|
||||
match self {
|
||||
LaneType::Driving => "a general-purpose driving lane",
|
||||
LaneType::Biking => "a protected bike lane",
|
||||
LaneType::Bus => "a bus-only lane",
|
||||
LaneType::Parking => "an on-street parking lane",
|
||||
LaneType::Sidewalk => "a sidewalk",
|
||||
LaneType::Shoulder => "a shoulder",
|
||||
LaneType::SharedLeftTurn => "a shared left-turn lane",
|
||||
LaneType::Construction => "a lane that's closed for construction",
|
||||
LaneType::LightRail => "a light rail track",
|
||||
LaneType::Buffer(BufferType::Stripes) => "striped pavement",
|
||||
LaneType::Buffer(BufferType::FlexPosts) => "flex post barriers",
|
||||
LaneType::Buffer(BufferType::Planters) => "planter barriers",
|
||||
LaneType::Buffer(BufferType::JerseyBarrier) => "a Jersey barrier",
|
||||
LaneType::Buffer(BufferType::Curb) => "a raised curb",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_name(self) -> &'static str {
|
||||
match self {
|
||||
LaneType::Driving => "driving lane",
|
||||
LaneType::Biking => "bike lane",
|
||||
LaneType::Bus => "bus lane",
|
||||
LaneType::Parking => "parking lane",
|
||||
LaneType::Sidewalk => "sidewalk",
|
||||
LaneType::Shoulder => "shoulder",
|
||||
LaneType::SharedLeftTurn => "left-turn lane",
|
||||
LaneType::Construction => "construction",
|
||||
LaneType::LightRail => "light rail track",
|
||||
LaneType::Buffer(BufferType::Stripes) => "stripes",
|
||||
LaneType::Buffer(BufferType::FlexPosts) => "flex posts",
|
||||
LaneType::Buffer(BufferType::Planters) => "planters",
|
||||
LaneType::Buffer(BufferType::JerseyBarrier) => "Jersey barrier",
|
||||
LaneType::Buffer(BufferType::Curb) => "curb",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_short_name(x: &str) -> Option<LaneType> {
|
||||
match x {
|
||||
"driving lane" => Some(LaneType::Driving),
|
||||
"bike lane" => Some(LaneType::Biking),
|
||||
"bus lane" => Some(LaneType::Bus),
|
||||
"parking lane" => Some(LaneType::Parking),
|
||||
"sidewalk" => Some(LaneType::Sidewalk),
|
||||
"shoulder" => Some(LaneType::Shoulder),
|
||||
"left-turn lane" => Some(LaneType::SharedLeftTurn),
|
||||
"construction" => Some(LaneType::Construction),
|
||||
"light rail track" => Some(LaneType::LightRail),
|
||||
"stripes" => Some(LaneType::Buffer(BufferType::Stripes)),
|
||||
"flex posts" => Some(LaneType::Buffer(BufferType::FlexPosts)),
|
||||
"planters" => Some(LaneType::Buffer(BufferType::Planters)),
|
||||
"Jersey barrier" => Some(LaneType::Buffer(BufferType::JerseyBarrier)),
|
||||
"curb" => Some(LaneType::Buffer(BufferType::Curb)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the lane type as a single character, for use in tests.
|
||||
pub fn to_char(self) -> char {
|
||||
match self {
|
||||
LaneType::Driving => 'd',
|
||||
LaneType::Biking => 'b',
|
||||
LaneType::Bus => 'B',
|
||||
LaneType::Parking => 'p',
|
||||
LaneType::Sidewalk => 's',
|
||||
LaneType::Shoulder => 'S',
|
||||
LaneType::SharedLeftTurn => 'C',
|
||||
LaneType::Construction => 'x',
|
||||
LaneType::LightRail => 'l',
|
||||
LaneType::Buffer(_) => '|',
|
||||
}
|
||||
}
|
||||
|
||||
/// The inverse of `to_char`. Always picks one buffer type. Panics on invalid input.
|
||||
pub fn from_char(x: char) -> LaneType {
|
||||
match x {
|
||||
'd' => LaneType::Driving,
|
||||
'b' => LaneType::Biking,
|
||||
'B' => LaneType::Bus,
|
||||
'p' => LaneType::Parking,
|
||||
's' => LaneType::Sidewalk,
|
||||
'S' => LaneType::Shoulder,
|
||||
'C' => LaneType::SharedLeftTurn,
|
||||
'x' => LaneType::Construction,
|
||||
'l' => LaneType::LightRail,
|
||||
'|' => LaneType::Buffer(BufferType::FlexPosts),
|
||||
_ => panic!("from_char({}) undefined", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LaneSpec {
|
||||
pub lt: LaneType,
|
||||
pub dir: Direction,
|
||||
pub width: Distance,
|
||||
}
|
||||
|
||||
impl LaneSpec {
|
||||
/// For a given lane type, returns some likely widths. This may depend on the type of the road,
|
||||
/// so the OSM tags are also passed in. The first value returned will be used as a default.
|
||||
pub fn typical_lane_widths(lt: LaneType, tags: &Tags) -> Vec<(Distance, &'static str)> {
|
||||
// These're cobbled together from various sources
|
||||
match lt {
|
||||
// https://en.wikipedia.org/wiki/Lane#Lane_width
|
||||
LaneType::Driving => {
|
||||
let mut choices = vec![
|
||||
(Distance::feet(8.0), "narrow"),
|
||||
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
|
||||
(Distance::feet(10.0), "typical"),
|
||||
(Distance::feet(12.0), "highway"),
|
||||
];
|
||||
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
|
||||
choices.swap(1, 0);
|
||||
}
|
||||
choices
|
||||
}
|
||||
// https://www.gov.uk/government/publications/cycle-infrastructure-design-ltn-120 table
|
||||
// 5-2
|
||||
LaneType::Biking => vec![
|
||||
(Distance::meters(2.0), "standard"),
|
||||
(Distance::meters(1.5), "absolute minimum"),
|
||||
],
|
||||
// https://nacto.org/publication/urban-street-design-guide/street-design-elements/transit-streets/dedicated-curbside-offset-bus-lanes/
|
||||
LaneType::Bus => vec![
|
||||
(Distance::feet(12.0), "normal"),
|
||||
(Distance::feet(10.0), "minimum"),
|
||||
],
|
||||
// https://nacto.org/publication/urban-street-design-guide/street-design-elements/lane-width/
|
||||
LaneType::Parking => {
|
||||
let mut choices = vec![
|
||||
(Distance::feet(7.0), "narrow"),
|
||||
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
|
||||
(Distance::feet(9.0), "wide"),
|
||||
(Distance::feet(15.0), "loading zone"),
|
||||
];
|
||||
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
|
||||
choices.swap(1, 0);
|
||||
}
|
||||
choices
|
||||
}
|
||||
// Just a guess
|
||||
LaneType::SharedLeftTurn => vec![(NORMAL_LANE_THICKNESS, "default")],
|
||||
// These're often converted from existing lanes, so just retain that width
|
||||
LaneType::Construction => vec![(NORMAL_LANE_THICKNESS, "default")],
|
||||
// No idea, just using this for now...
|
||||
LaneType::LightRail => vec![(NORMAL_LANE_THICKNESS, "default")],
|
||||
// http://www.seattle.gov/rowmanual/manual/4_11.asp
|
||||
LaneType::Sidewalk => vec![
|
||||
(SIDEWALK_THICKNESS, "default"),
|
||||
(Distance::feet(6.0), "wide"),
|
||||
],
|
||||
LaneType::Shoulder => vec![(SHOULDER_THICKNESS, "default")],
|
||||
// Pretty wild guesses
|
||||
LaneType::Buffer(BufferType::Stripes) => vec![(Distance::meters(1.5), "default")],
|
||||
LaneType::Buffer(BufferType::FlexPosts) => {
|
||||
vec![(Distance::meters(1.5), "default")]
|
||||
}
|
||||
LaneType::Buffer(BufferType::Planters) => {
|
||||
vec![(Distance::meters(2.0), "default")]
|
||||
}
|
||||
LaneType::Buffer(BufferType::JerseyBarrier) => {
|
||||
vec![(Distance::meters(1.5), "default")]
|
||||
}
|
||||
LaneType::Buffer(BufferType::Curb) => vec![(Distance::meters(0.5), "default")],
|
||||
}
|
||||
}
|
||||
|
||||
/// Put a list of forward and backward lanes into left-to-right order, depending on the driving
|
||||
/// side. Both input lists should be ordered from the center of the road going outwards.
|
||||
pub(crate) fn assemble_ltr(
|
||||
mut fwd_side: Vec<LaneSpec>,
|
||||
mut back_side: Vec<LaneSpec>,
|
||||
driving_side: DrivingSide,
|
||||
) -> Vec<LaneSpec> {
|
||||
match driving_side {
|
||||
DrivingSide::Right => {
|
||||
back_side.reverse();
|
||||
back_side.extend(fwd_side);
|
||||
back_side
|
||||
}
|
||||
DrivingSide::Left => {
|
||||
fwd_side.reverse();
|
||||
fwd_side.extend(back_side);
|
||||
fwd_side
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// None if bidirectional. If it's one-way, which direction is that relative to the road?
|
||||
/// (Usually forwards)
|
||||
pub fn oneway_for_driving(lanes: &[LaneSpec]) -> Option<Direction> {
|
||||
let mut fwd = false;
|
||||
let mut back = false;
|
||||
for x in lanes {
|
||||
if x.lt == LaneType::Driving {
|
||||
if x.dir == Direction::Fwd {
|
||||
fwd = true;
|
||||
} else {
|
||||
back = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if fwd && back {
|
||||
// Bidirectional
|
||||
None
|
||||
} else if fwd {
|
||||
Some(Direction::Fwd)
|
||||
} else if back {
|
||||
Some(Direction::Back)
|
||||
} else {
|
||||
// Not driveable at all
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user