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"
|
name = "raw_map"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aabb-quadtree",
|
|
||||||
"abstio",
|
"abstio",
|
||||||
"abstutil",
|
"abstutil",
|
||||||
"anyhow",
|
|
||||||
"geojson",
|
|
||||||
"geom",
|
"geom",
|
||||||
"kml",
|
|
||||||
"log",
|
|
||||||
"petgraph",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"street_network",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
@ -3711,6 +3705,23 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -17,6 +17,7 @@ members = [
|
|||||||
"popdat",
|
"popdat",
|
||||||
"raw_map",
|
"raw_map",
|
||||||
"sim",
|
"sim",
|
||||||
|
"street_network",
|
||||||
"synthpop",
|
"synthpop",
|
||||||
"tests",
|
"tests",
|
||||||
"traffic_seitan",
|
"traffic_seitan",
|
||||||
|
@ -54,7 +54,7 @@ impl MainState {
|
|||||||
.to_string(),
|
.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());
|
ctx.canvas.map_dims = (bounds.width(), bounds.height());
|
||||||
|
|
||||||
let mut state = MainState {
|
let mut state = MainState {
|
||||||
@ -263,14 +263,17 @@ impl State<App> for MainState {
|
|||||||
self.mode = Mode::SetBoundaryPt1;
|
self.mode = Mode::SetBoundaryPt1;
|
||||||
}
|
}
|
||||||
"detect short roads" => {
|
"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_deleted(r);
|
||||||
app.model.road_added(ctx, r);
|
app.model.road_added(ctx, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"simplify RawMap" => {
|
"simplify RawMap" => {
|
||||||
ctx.loading_screen("simplify", |ctx, timer| {
|
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);
|
app.model.recreate_world(ctx, timer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -306,7 +309,7 @@ impl State<App> for MainState {
|
|||||||
}
|
}
|
||||||
Mode::CreatingRoad(i1) => {
|
Mode::CreatingRoad(i1) => {
|
||||||
if ctx.canvas_movement() {
|
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) {
|
if ctx.input.pressed(Key::Escape) {
|
||||||
@ -322,7 +325,7 @@ impl State<App> for MainState {
|
|||||||
}
|
}
|
||||||
Mode::SetBoundaryPt1 => {
|
Mode::SetBoundaryPt1 => {
|
||||||
if ctx.canvas_movement() {
|
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();
|
let mut txt = Text::new();
|
||||||
@ -341,7 +344,7 @@ impl State<App> for MainState {
|
|||||||
}
|
}
|
||||||
Mode::SetBoundaryPt2(pt1) => {
|
Mode::SetBoundaryPt2(pt1) => {
|
||||||
if ctx.canvas_movement() {
|
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();
|
let mut txt = Text::new();
|
||||||
@ -371,7 +374,7 @@ impl State<App> for MainState {
|
|||||||
|
|
||||||
g.draw_polygon(
|
g.draw_polygon(
|
||||||
Color::rgb(242, 239, 233),
|
Color::rgb(242, 239, 233),
|
||||||
app.model.map.boundary_polygon.clone(),
|
app.model.map.streets.boundary_polygon.clone(),
|
||||||
);
|
);
|
||||||
app.model.world.draw(g);
|
app.model.world.draw(g);
|
||||||
g.redraw(&app.model.draw_extra);
|
g.redraw(&app.model.draw_extra);
|
||||||
@ -380,7 +383,8 @@ impl State<App> for MainState {
|
|||||||
Mode::Neutral | Mode::SetBoundaryPt1 => {}
|
Mode::Neutral | Mode::SetBoundaryPt1 => {}
|
||||||
Mode::CreatingRoad(i1) => {
|
Mode::CreatingRoad(i1) => {
|
||||||
if let Some(cursor) = g.get_cursor_in_map_space() {
|
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)));
|
g.draw_polygon(Color::GREEN, l.make_polygons(Distance::meters(5.0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,10 @@ pub struct EditRoad {
|
|||||||
|
|
||||||
impl EditRoad {
|
impl EditRoad {
|
||||||
pub(crate) fn new_state(ctx: &mut EventCtx, app: &App, r: OriginalRoad) -> Box<dyn State<App>> {
|
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();
|
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(
|
batch.push(
|
||||||
Color::BLACK,
|
Color::BLACK,
|
||||||
pl.make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
|
pl.make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
|
||||||
@ -32,7 +32,7 @@ impl EditRoad {
|
|||||||
"Length before trimming: {}",
|
"Length before trimming: {}",
|
||||||
road.untrimmed_road_geometry().0.length()
|
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())));
|
txt.add_line(Line(format!("Length after trimming: {}", pl.length())));
|
||||||
}
|
}
|
||||||
for (rt, to) in &road.turn_restrictions {
|
for (rt, to) in &road.turn_restrictions {
|
||||||
@ -160,7 +160,7 @@ impl SimpleState<App> for EditRoad {
|
|||||||
"Apply" => {
|
"Apply" => {
|
||||||
app.model.road_deleted(self.r);
|
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("lanes");
|
||||||
road.osm_tags.remove("oneway");
|
road.osm_tags.remove("oneway");
|
||||||
@ -200,7 +200,7 @@ impl SimpleState<App> for EditRoad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
road.lane_specs_ltr =
|
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);
|
app.model.road_added(ctx, self.r);
|
||||||
Transition::Pop
|
Transition::Pop
|
||||||
|
@ -81,7 +81,7 @@ pub fn load_map(
|
|||||||
if !URLManager::change_camera(
|
if !URLManager::change_camera(
|
||||||
ctx,
|
ctx,
|
||||||
center_camera.as_ref(),
|
center_camera.as_ref(),
|
||||||
&app.model.map.gps_bounds,
|
&app.model.map.streets.gps_bounds,
|
||||||
) && !app.model.map.name.map.is_empty()
|
) && !app.model.map.name.map.is_empty()
|
||||||
{
|
{
|
||||||
CameraState::load(ctx, &app.model.map.name);
|
CameraState::load(ctx, &app.model.map.name);
|
||||||
|
@ -72,14 +72,21 @@ impl Model {
|
|||||||
}
|
}
|
||||||
timer.start_iter(
|
timer.start_iter(
|
||||||
"fill out world with intersections",
|
"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();
|
timer.next();
|
||||||
self.intersection_added(ctx, id);
|
self.intersection_added(ctx, id);
|
||||||
}
|
}
|
||||||
timer.start_iter("fill out world with roads", self.map.roads.len());
|
timer.start_iter("fill out world with roads", self.map.streets.roads.len());
|
||||||
for id in self.map.roads.keys().cloned().collect::<Vec<_>>() {
|
for id in self.map.streets.roads.keys().cloned().collect::<Vec<_>>() {
|
||||||
timer.next();
|
timer.next();
|
||||||
self.road_added(ctx, id);
|
self.road_added(ctx, id);
|
||||||
}
|
}
|
||||||
@ -100,10 +107,10 @@ impl Model {
|
|||||||
for b in self.map.buildings.values_mut() {
|
for b in self.map.buildings.values_mut() {
|
||||||
b.polygon = b.polygon.translate(-top_left.x(), -top_left.y());
|
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());
|
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 {
|
for pt in &mut r.osm_center_points {
|
||||||
*pt = pt.offset(-top_left.x(), -top_left.y());
|
*pt = pt.offset(-top_left.x(), -top_left.y());
|
||||||
}
|
}
|
||||||
@ -111,16 +118,22 @@ impl Model {
|
|||||||
let pt1 = Pt2D::new(0.0, 0.0);
|
let pt1 = Pt2D::new(0.0, 0.0);
|
||||||
let pt2 = bottom_right.offset(-top_left.x(), -top_left.y());
|
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
|
// Make gps_bounds sane
|
||||||
let mut seattle_bounds = GPSBounds::new();
|
let mut seattle_bounds = GPSBounds::new();
|
||||||
seattle_bounds.update(LonLat::new(-122.453224, 47.723277));
|
seattle_bounds.update(LonLat::new(-122.453224, 47.723277));
|
||||||
seattle_bounds.update(LonLat::new(-122.240505, 47.495342));
|
seattle_bounds.update(LonLat::new(-122.240505, 47.495342));
|
||||||
|
|
||||||
self.map.gps_bounds = GPSBounds::new();
|
self.map.streets.gps_bounds = GPSBounds::new();
|
||||||
self.map.gps_bounds.update(pt1.to_gps(&seattle_bounds));
|
self.map
|
||||||
self.map.gps_bounds.update(pt2.to_gps(&seattle_bounds));
|
.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());
|
self.recreate_world(ctx, &mut Timer::throwaway());
|
||||||
}
|
}
|
||||||
@ -132,10 +145,10 @@ impl Model {
|
|||||||
bounds.update(*pt);
|
bounds.update(*pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i in self.map.intersections.values() {
|
for i in self.map.streets.intersections.values() {
|
||||||
bounds.update(i.point);
|
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 {
|
for pt in &r.osm_center_points {
|
||||||
bounds.update(*pt);
|
bounds.update(*pt);
|
||||||
}
|
}
|
||||||
@ -147,7 +160,7 @@ impl Model {
|
|||||||
// Intersections
|
// Intersections
|
||||||
impl Model {
|
impl Model {
|
||||||
fn intersection_added(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
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 {
|
let color = match i.intersection_type {
|
||||||
IntersectionType::TrafficSignal => Color::GREEN,
|
IntersectionType::TrafficSignal => Color::GREEN,
|
||||||
IntersectionType::StopSign => Color::RED,
|
IntersectionType::StopSign => Color::RED,
|
||||||
@ -155,17 +168,18 @@ impl Model {
|
|||||||
IntersectionType::Construction => Color::ORANGE,
|
IntersectionType::Construction => Color::ORANGE,
|
||||||
};
|
};
|
||||||
|
|
||||||
let poly = if self.intersection_geom && !self.map.roads_per_intersection(id).is_empty() {
|
let poly =
|
||||||
match self.map.preview_intersection(id) {
|
if self.intersection_geom && !self.map.streets.roads_per_intersection(id).is_empty() {
|
||||||
Ok((poly, _, _)) => poly,
|
match self.map.streets.preview_intersection(id) {
|
||||||
Err(err) => {
|
Ok((poly, _, _)) => poly,
|
||||||
error!("No geometry for {}: {}", id, err);
|
Err(err) => {
|
||||||
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
error!("No geometry for {}: {}", id, err);
|
||||||
|
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
||||||
Circle::new(i.point, INTERSECTION_RADIUS).to_polygon()
|
};
|
||||||
};
|
|
||||||
|
|
||||||
self.world
|
self.world
|
||||||
.add(ID::Intersection(id))
|
.add(ID::Intersection(id))
|
||||||
@ -183,8 +197,9 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_i(&mut self, ctx: &EventCtx, point: Pt2D) {
|
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
|
self.map
|
||||||
|
.streets
|
||||||
.intersections
|
.intersections
|
||||||
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
|
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
|
||||||
self.intersection_added(ctx, id);
|
self.intersection_added(ctx, id);
|
||||||
@ -192,7 +207,7 @@ impl Model {
|
|||||||
|
|
||||||
pub fn move_i(&mut self, ctx: &EventCtx, id: osm::NodeID, point: Pt2D) {
|
pub fn move_i(&mut self, ctx: &EventCtx, id: osm::NodeID, point: Pt2D) {
|
||||||
self.world.delete_before_replacement(ID::Intersection(id));
|
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_deleted(r);
|
||||||
self.road_added(ctx, r);
|
self.road_added(ctx, r);
|
||||||
}
|
}
|
||||||
@ -200,18 +215,18 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_i(&mut self, id: osm::NodeID) {
|
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");
|
error!("Can't delete intersection used by roads");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.map.delete_intersection(id);
|
self.map.streets.delete_intersection(id);
|
||||||
self.world.delete(ID::Intersection(id));
|
self.world.delete(ID::Intersection(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_i(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
pub fn toggle_i(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
||||||
self.world.delete_before_replacement(ID::Intersection(id));
|
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 {
|
if i.intersection_type == IntersectionType::TrafficSignal {
|
||||||
i.intersection_type = IntersectionType::StopSign;
|
i.intersection_type = IntersectionType::StopSign;
|
||||||
} else if i.intersection_type == IntersectionType::StopSign {
|
} else if i.intersection_type == IntersectionType::StopSign {
|
||||||
@ -225,8 +240,18 @@ impl Model {
|
|||||||
self.intersection_geom = show;
|
self.intersection_geom = show;
|
||||||
|
|
||||||
ctx.loading_screen("show intersection geometry", |ctx, timer| {
|
ctx.loading_screen("show intersection geometry", |ctx, timer| {
|
||||||
timer.start_iter("intersection geometry", self.map.intersections.len());
|
timer.start_iter(
|
||||||
for id in self.map.intersections.keys().cloned().collect::<Vec<_>>() {
|
"intersection geometry",
|
||||||
|
self.map.streets.intersections.len(),
|
||||||
|
);
|
||||||
|
for id in self
|
||||||
|
.map
|
||||||
|
.streets
|
||||||
|
.intersections
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
{
|
||||||
timer.next();
|
timer.next();
|
||||||
self.world.delete_before_replacement(ID::Intersection(id));
|
self.world.delete_before_replacement(ID::Intersection(id));
|
||||||
self.intersection_added(ctx, id);
|
self.intersection_added(ctx, id);
|
||||||
@ -239,7 +264,7 @@ impl Model {
|
|||||||
|
|
||||||
pub fn debug_intersection_geometry(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
pub fn debug_intersection_geometry(&mut self, ctx: &EventCtx, id: osm::NodeID) {
|
||||||
let mut batch = GeomBatch::new();
|
let mut batch = GeomBatch::new();
|
||||||
match self.map.preview_intersection(id) {
|
match self.map.streets.preview_intersection(id) {
|
||||||
Ok((_, _, labels)) => {
|
Ok((_, _, labels)) => {
|
||||||
for (label, polygon) in labels {
|
for (label, polygon) in labels {
|
||||||
let txt_batch = Text::from(Line(label).fg(Color::CYAN))
|
let txt_batch = Text::from(Line(label).fg(Color::CYAN))
|
||||||
@ -261,7 +286,7 @@ impl Model {
|
|||||||
// Roads
|
// Roads
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn road_added(&mut self, ctx: &EventCtx, id: OriginalRoad) {
|
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 (center, total_width) = road.untrimmed_road_geometry();
|
||||||
let hitbox = center.make_polygons(total_width);
|
let hitbox = center.make_polygons(total_width);
|
||||||
let mut draw = GeomBatch::new();
|
let mut draw = GeomBatch::new();
|
||||||
@ -301,6 +326,7 @@ impl Model {
|
|||||||
// Ban cul-de-sacs, since they get stripped out later anyway.
|
// Ban cul-de-sacs, since they get stripped out later anyway.
|
||||||
if self
|
if self
|
||||||
.map
|
.map
|
||||||
|
.streets
|
||||||
.roads
|
.roads
|
||||||
.keys()
|
.keys()
|
||||||
.any(|r| (r.i1 == i1 && r.i2 == i2) || (r.i1 == i2 && r.i2 == i1))
|
.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(
|
let road = match RawRoad::new(
|
||||||
vec![
|
vec![
|
||||||
self.map.intersections[&i1].point,
|
self.map.streets.intersections[&i1].point,
|
||||||
self.map.intersections[&i2].point,
|
self.map.streets.intersections[&i2].point,
|
||||||
],
|
],
|
||||||
osm_tags,
|
osm_tags,
|
||||||
&self.map.config,
|
&self.map.streets.config,
|
||||||
) {
|
) {
|
||||||
Ok(road) => road,
|
Ok(road) => road,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -344,7 +370,7 @@ impl Model {
|
|||||||
self.world.delete_before_replacement(ID::Intersection(i1));
|
self.world.delete_before_replacement(ID::Intersection(i1));
|
||||||
self.world.delete_before_replacement(ID::Intersection(i2));
|
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.road_added(ctx, id);
|
||||||
|
|
||||||
self.intersection_added(ctx, i1);
|
self.intersection_added(ctx, i1);
|
||||||
@ -358,7 +384,7 @@ impl Model {
|
|||||||
.delete_before_replacement(ID::Intersection(id.i1));
|
.delete_before_replacement(ID::Intersection(id.i1));
|
||||||
self.world
|
self.world
|
||||||
.delete_before_replacement(ID::Intersection(id.i2));
|
.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.i1);
|
||||||
self.intersection_added(ctx, id.i2);
|
self.intersection_added(ctx, id.i2);
|
||||||
@ -373,7 +399,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
self.showing_pts = Some(id);
|
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() {
|
for (idx, pt) in r.osm_center_points.iter().enumerate() {
|
||||||
// Don't show handles for the intersections
|
// Don't show handles for the intersections
|
||||||
if idx != 0 && idx != r.osm_center_points.len() - 1 {
|
if idx != 0 && idx != r.osm_center_points.len() - 1 {
|
||||||
@ -395,7 +421,7 @@ impl Model {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.showing_pts = None;
|
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));
|
self.world.delete(ID::RoadPoint(id, idx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,7 +430,7 @@ impl Model {
|
|||||||
assert_eq!(self.showing_pts, Some(id));
|
assert_eq!(self.showing_pts, Some(id));
|
||||||
// stop_showing_pts deletes the points, but we want to use delete_before_replacement
|
// stop_showing_pts deletes the points, but we want to use delete_before_replacement
|
||||||
self.showing_pts = None;
|
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));
|
self.world.delete_before_replacement(ID::RoadPoint(id, idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +440,13 @@ impl Model {
|
|||||||
self.world
|
self.world
|
||||||
.delete_before_replacement(ID::Intersection(id.i2));
|
.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;
|
pts[idx] = point;
|
||||||
|
|
||||||
self.road_added(ctx, id);
|
self.road_added(ctx, id);
|
||||||
@ -438,7 +470,13 @@ impl Model {
|
|||||||
self.world
|
self.world
|
||||||
.delete_before_replacement(ID::Intersection(id.i2));
|
.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);
|
transform(pts);
|
||||||
|
|
||||||
self.road_added(ctx, id);
|
self.road_added(ctx, id);
|
||||||
@ -477,7 +515,7 @@ impl Model {
|
|||||||
self.stop_showing_pts(id);
|
self.stop_showing_pts(id);
|
||||||
|
|
||||||
let (retained_i, deleted_i, deleted_roads, created_roads) =
|
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)) => {
|
Ok((retained_i, deleted_i, deleted_roads, created_roads)) => {
|
||||||
(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) {
|
pub fn toggle_junction(&mut self, ctx: &EventCtx, id: OriginalRoad) {
|
||||||
self.road_deleted(id);
|
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") {
|
if road.osm_tags.is("junction", "intersection") {
|
||||||
road.osm_tags.remove("junction");
|
road.osm_tags.remove("junction");
|
||||||
} else {
|
} else {
|
||||||
@ -592,14 +630,14 @@ fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
|
|||||||
f,
|
f,
|
||||||
r#"<!-- If you couldn't tell, this is a fake .osm file not representing the real world. -->"#
|
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!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
r#" <bounds minlon="{}" maxlon="{}" minlat="{}" maxlat="{}"/>"#,
|
r#" <bounds minlon="{}" maxlon="{}" minlat="{}" maxlat="{}"/>"#,
|
||||||
b.min_lon, b.max_lon, b.min_lat, b.max_lat
|
b.min_lon, b.max_lon, b.min_lat, b.max_lat
|
||||||
)?;
|
)?;
|
||||||
let mut pt_to_id: HashMap<HashablePt2D, osm::NodeID> = HashMap::new();
|
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);
|
pt_to_id.insert(i.point.to_hashable(), *id);
|
||||||
let pt = i.point.to_gps(b);
|
let pt = i.point.to_gps(b);
|
||||||
writeln!(
|
writeln!(
|
||||||
@ -610,7 +648,7 @@ fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
|
|||||||
pt.y()
|
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)?;
|
writeln!(f, r#" <way id="{}">"#, id.osm_way_id.0)?;
|
||||||
for pt in &r.osm_center_points {
|
for pt in &r.osm_center_points {
|
||||||
// TODO Make new IDs if needed
|
// 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");
|
timer.start("clipping map to boundary");
|
||||||
|
|
||||||
// So we can use retain without borrowing issues
|
// 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());
|
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
|
// This is kind of indirect and slow, but first pass -- just remove roads that start or end
|
||||||
// outside the boundary polygon.
|
// 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 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 last_in = boundary_polygon.contains_pt(*r.osm_center_points.last().unwrap());
|
||||||
let light_rail_ok = if r.is_light_rail() {
|
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();
|
let mut extra_borders: BTreeMap<osm::NodeID, osm::NodeID> = BTreeMap::new();
|
||||||
|
|
||||||
// First pass: Clip roads beginning out of bounds
|
// 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 {
|
for id in road_ids {
|
||||||
let r = &map.roads[&id];
|
let r = &map.streets.roads[&id];
|
||||||
if map.boundary_polygon.contains_pt(r.osm_center_points[0]) {
|
if map
|
||||||
|
.streets
|
||||||
|
.boundary_polygon
|
||||||
|
.contains_pt(r.osm_center_points[0])
|
||||||
|
{
|
||||||
continue;
|
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
|
// disconnects two roads in the map that would be connected if we left in some
|
||||||
// partly-out-of-bounds road.
|
// partly-out-of-bounds road.
|
||||||
if map
|
if map
|
||||||
|
.streets
|
||||||
.roads
|
.roads
|
||||||
.keys()
|
.keys()
|
||||||
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
|
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
|
||||||
.count()
|
.count()
|
||||||
> 1
|
> 1
|
||||||
{
|
{
|
||||||
let copy = map.intersections[&move_i].clone();
|
let copy = map.streets.intersections[&move_i].clone();
|
||||||
// Don't conflict with OSM IDs
|
// 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);
|
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);
|
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;
|
i.intersection_type = IntersectionType::Border;
|
||||||
|
|
||||||
// Now trim it.
|
// 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 center = PolyLine::must_new(mut_r.osm_center_points.clone());
|
||||||
let border_pt = boundary_ring.all_intersections(¢er)[0];
|
let border_pt = boundary_ring.all_intersections(¢er)[0];
|
||||||
if let Some(pl) = center.reversed().get_slice_ending_at(border_pt) {
|
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);
|
panic!("{} interacts with border strangely", id);
|
||||||
}
|
}
|
||||||
i.point = mut_r.osm_center_points[0];
|
i.point = mut_r.osm_center_points[0];
|
||||||
map.roads.insert(
|
map.streets.roads.insert(
|
||||||
OriginalRoad {
|
OriginalRoad {
|
||||||
osm_way_id: id.osm_way_id,
|
osm_way_id: id.osm_way_id,
|
||||||
i1: move_i,
|
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
|
// 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 {
|
for id in road_ids {
|
||||||
let r = &map.roads[&id];
|
let r = &map.streets.roads[&id];
|
||||||
if map
|
if map
|
||||||
|
.streets
|
||||||
.boundary_polygon
|
.boundary_polygon
|
||||||
.contains_pt(*r.osm_center_points.last().unwrap())
|
.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
|
// disconnects two roads in the map that would be connected if we left in some
|
||||||
// partly-out-of-bounds road.
|
// partly-out-of-bounds road.
|
||||||
if map
|
if map
|
||||||
|
.streets
|
||||||
.roads
|
.roads
|
||||||
.keys()
|
.keys()
|
||||||
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
|
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
|
||||||
.count()
|
.count()
|
||||||
> 1
|
> 1
|
||||||
{
|
{
|
||||||
let copy = map.intersections[&move_i].clone();
|
let copy = map.streets.intersections[&move_i].clone();
|
||||||
move_i = map.new_osm_node_id(-1);
|
move_i = map.streets.new_osm_node_id(-1);
|
||||||
extra_borders.insert(orig_id, move_i);
|
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);
|
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;
|
i.intersection_type = IntersectionType::Border;
|
||||||
|
|
||||||
// Now trim it.
|
// 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 center = PolyLine::must_new(mut_r.osm_center_points.clone());
|
||||||
let border_pt = boundary_ring.all_intersections(¢er.reversed())[0];
|
let border_pt = boundary_ring.all_intersections(¢er.reversed())[0];
|
||||||
if let Some(pl) = center.get_slice_ending_at(border_pt) {
|
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);
|
panic!("{} interacts with border strangely", id);
|
||||||
}
|
}
|
||||||
i.point = *mut_r.osm_center_points.last().unwrap();
|
i.point = *mut_r.osm_center_points.last().unwrap();
|
||||||
map.roads.insert(
|
map.streets.roads.insert(
|
||||||
OriginalRoad {
|
OriginalRoad {
|
||||||
osm_way_id: id.osm_way_id,
|
osm_way_id: id.osm_way_id,
|
||||||
i1: id.i1,
|
i1: id.i1,
|
||||||
@ -149,7 +156,11 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
|
|||||||
|
|
||||||
let mut result_areas = Vec::new();
|
let mut result_areas = Vec::new();
|
||||||
for orig_area in map.areas.drain(..) {
|
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();
|
let mut area = orig_area.clone();
|
||||||
area.polygon = polygon;
|
area.polygon = polygon;
|
||||||
result_areas.push(area);
|
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.
|
// TODO Don't touch parking lots. It'll be visually obvious if a clip intersects one of these.
|
||||||
// The boundary should be manually adjusted.
|
// 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");
|
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)?;
|
fs_err::create_dir_all(input)?;
|
||||||
let mut f = BufWriter::new(File::create(format!("{input}/query"))?);
|
let mut f = BufWriter::new(File::create(format!("{input}/query"))?);
|
||||||
let mut ids = Vec::new();
|
let mut ids = Vec::new();
|
||||||
for (id, r) in &map.roads {
|
for (id, r) in &map.streets.roads {
|
||||||
// TODO Handle cul-de-sacs
|
// TODO Handle cul-de-sacs
|
||||||
if let Ok(pl) = PolyLine::new(r.osm_center_points.clone()) {
|
if let Ok(pl) = PolyLine::new(r.osm_center_points.clone()) {
|
||||||
ids.push(*id);
|
ids.push(*id);
|
||||||
@ -84,7 +84,13 @@ fn generate_input(input: &str, map: &RawMap) -> Result<Vec<OriginalRoad>> {
|
|||||||
if *pts.last().unwrap() != pl.last_pt() {
|
if *pts.last().unwrap() != pl.last_pt() {
|
||||||
pts.push(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())?;
|
write!(f, "{},{}", gps.x(), gps.y())?;
|
||||||
if idx != pts.len() - 1 {
|
if idx != pts.len() - 1 {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
@ -127,8 +133,8 @@ fn scrape_output(output: &str, map: &mut RawMap, ids: Vec<OriginalRoad>) -> Resu
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// TODO Also put total_climb and total_descent on the roads
|
// TODO Also put total_climb and total_descent on the roads
|
||||||
map.intersections.get_mut(&id.i1).unwrap().elevation = values[0];
|
map.streets.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.i2).unwrap().elevation = values[1];
|
||||||
}
|
}
|
||||||
if cnt != num_ids {
|
if cnt != num_ids {
|
||||||
bail!("Output had {} lines, but we made {} queries", 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
|
// 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.
|
// geometry. If we did this after trimming, we'd miss some of the horizontal distance.
|
||||||
for (id, road) in &mut map.roads {
|
for (id, road) in &mut map.streets.roads {
|
||||||
let rise = map.intersections[&id.i2].elevation - map.intersections[&id.i1].elevation;
|
let rise = map.streets.intersections[&id.i2].elevation
|
||||||
|
- map.streets.intersections[&id.i1].elevation;
|
||||||
let run = road.length();
|
let run = road.length();
|
||||||
if !(rise / run).is_finite() {
|
if !(rise / run).is_finite() {
|
||||||
// TODO Warn?
|
// TODO Warn?
|
||||||
|
@ -40,7 +40,7 @@ pub fn extract_osm(
|
|||||||
opts: &Options,
|
opts: &Options,
|
||||||
timer: &mut Timer,
|
timer: &mut Timer,
|
||||||
) -> OsmExtract {
|
) -> 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
|
// TODO Hacks to override OSM data. There's no problem upstream, but we want to accomplish
|
||||||
// various things for A/B Street.
|
// various things for A/B Street.
|
||||||
@ -57,8 +57,8 @@ pub fn extract_osm(
|
|||||||
|
|
||||||
if clip_path.is_none() {
|
if clip_path.is_none() {
|
||||||
// Use the boundary from .osm.
|
// Use the boundary from .osm.
|
||||||
map.gps_bounds = doc.gps_bounds.clone();
|
map.streets.gps_bounds = doc.gps_bounds.clone();
|
||||||
map.boundary_polygon = map.gps_bounds.to_bounds().get_rectangle();
|
map.streets.boundary_polygon = map.streets.gps_bounds.to_bounds().get_rectangle();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out = OsmExtract {
|
let mut out = OsmExtract {
|
||||||
@ -128,7 +128,7 @@ pub fn extract_osm(
|
|||||||
.is_any(osm::HIGHWAY, vec!["cycleway", "footway", "path"])
|
.is_any(osm::HIGHWAY, vec!["cycleway", "footway", "path"])
|
||||||
{
|
{
|
||||||
extra_footways.shapes.push(ExtraShape {
|
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(),
|
attributes: way.tags.inner().clone(),
|
||||||
});
|
});
|
||||||
} else if way.tags.is("natural", "coastline") && !way.tags.is("place", "island") {
|
} 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);
|
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
|
// TODO Fill this out in a separate loop to keep a mutable borrow short. Maybe do this in
|
||||||
// reader, or stop doing this entirely.
|
// 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
|
// Look for any service roads that collide with parking lots, and treat them as parking aisles
|
||||||
// instead.
|
// instead.
|
||||||
fn find_parking_aisles(map: &mut RawMap, roads: &mut Vec<(WayID, Vec<Pt2D>, Tags)>) {
|
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() {
|
for (idx, lot) in map.parking_lots.iter().enumerate() {
|
||||||
closest.add(idx, lot.polygon.points());
|
closest.add(idx, lot.polygon.points());
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ pub fn import(map: &mut RawMap) -> Result<()> {
|
|||||||
.deserialize()
|
.deserialize()
|
||||||
{
|
{
|
||||||
let rec: Shape = rec?;
|
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
|
raw_shapes
|
||||||
.entry(rec.shape_id)
|
.entry(rec.shape_id)
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
@ -146,8 +146,8 @@ pub fn import(map: &mut RawMap) -> Result<()> {
|
|||||||
{
|
{
|
||||||
let rec: Stop = rec?;
|
let rec: Stop = rec?;
|
||||||
if stop_ids.contains(&rec.stop_id) {
|
if stop_ids.contains(&rec.stop_id) {
|
||||||
let position = LonLat::new(rec.stop_lon, rec.stop_lat).to_pt(&map.gps_bounds);
|
let position = LonLat::new(rec.stop_lon, rec.stop_lat).to_pt(&map.streets.gps_bounds);
|
||||||
if map.boundary_polygon.contains_pt(position) {
|
if map.streets.boundary_polygon.contains_pt(position) {
|
||||||
map.transit_stops.insert(
|
map.transit_stops.insert(
|
||||||
rec.stop_id.0.clone(),
|
rec.stop_id.0.clone(),
|
||||||
RawTransitStop {
|
RawTransitStop {
|
||||||
@ -234,7 +234,7 @@ fn dump_kml(map: &RawMap) {
|
|||||||
|
|
||||||
// One polyline per route
|
// One polyline per route
|
||||||
for route in &map.transit_routes {
|
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();
|
let mut attributes = BTreeMap::new();
|
||||||
attributes.insert("long_name".to_string(), route.long_name.clone());
|
attributes.insert("long_name".to_string(), route.long_name.clone());
|
||||||
attributes.insert("short_name".to_string(), route.short_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();
|
let mut attributes = BTreeMap::new();
|
||||||
attributes.insert("gtfs_id".to_string(), stop.gtfs_id.clone());
|
attributes.insert("gtfs_id".to_string(), stop.gtfs_id.clone());
|
||||||
attributes.insert("name".to_string(), stop.name.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 });
|
shapes.push(ExtraShape { points, attributes });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ impl Options {
|
|||||||
inferred_sidewalks: true,
|
inferred_sidewalks: true,
|
||||||
street_parking_spot_length: Distance::meters(8.0),
|
street_parking_spot_length: Distance::meters(8.0),
|
||||||
turn_on_red: true,
|
turn_on_red: true,
|
||||||
|
find_dog_legs_experiment: false,
|
||||||
},
|
},
|
||||||
onstreet_parking: OnstreetParking::JustOSM,
|
onstreet_parking: OnstreetParking::JustOSM,
|
||||||
public_offstreet_parking: PublicOffstreetParking::None,
|
public_offstreet_parking: PublicOffstreetParking::None,
|
||||||
@ -111,13 +112,13 @@ pub fn convert(
|
|||||||
) -> RawMap {
|
) -> RawMap {
|
||||||
let mut map = RawMap::blank(name);
|
let mut map = RawMap::blank(name);
|
||||||
// Do this early. Calculating RawRoads uses DrivingSide, for example!
|
// 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 {
|
if let Some(ref path) = clip_path {
|
||||||
let pts = LonLat::read_osmosis_polygon(path).unwrap();
|
let pts = LonLat::read_osmosis_polygon(path).unwrap();
|
||||||
let gps_bounds = GPSBounds::from(pts.clone());
|
let gps_bounds = GPSBounds::from(pts.clone());
|
||||||
map.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
|
map.streets.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
|
||||||
map.gps_bounds = gps_bounds;
|
map.streets.gps_bounds = gps_bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
let extract = extract::extract_osm(&mut map, &osm_input_path, clip_path, &opts, timer);
|
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
|
// 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.
|
// 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);
|
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) {
|
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 {
|
for (id, b) in &map.buildings {
|
||||||
closest.add(*id, b.polygon.points());
|
closest.add(*id, b.polygon.points());
|
||||||
}
|
}
|
||||||
@ -191,7 +193,7 @@ fn add_extra_buildings(map: &mut RawMap, path: &str) -> Result<()> {
|
|||||||
let mut id = -1;
|
let mut id = -1;
|
||||||
for (polygon, _) in Polygon::from_geojson_bytes(
|
for (polygon, _) in Polygon::from_geojson_bytes(
|
||||||
&abstio::slurp_file(path)?,
|
&abstio::slurp_file(path)?,
|
||||||
&map.gps_bounds,
|
&map.streets.gps_bounds,
|
||||||
require_in_bounds,
|
require_in_bounds,
|
||||||
)? {
|
)? {
|
||||||
// Add these as new buildings, generating a new dummy OSM ID.
|
// 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
|
// Normally we assume every road has a crosswalk, but since this map is configured to use OSM
|
||||||
// crossing nodes, let's reverse that assumption.
|
// 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_forward = false;
|
||||||
road.crosswalk_backward = false;
|
road.crosswalk_backward = false;
|
||||||
}
|
}
|
||||||
@ -231,7 +233,10 @@ fn filter_crosswalks(
|
|||||||
timer.next();
|
timer.next();
|
||||||
// Some crossing nodes are outside the map boundary or otherwise not on a road that we
|
// Some crossing nodes are outside the map boundary or otherwise not on a road that we
|
||||||
// retained
|
// 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
|
// TODO Support cul-de-sacs and other loop roads
|
||||||
if let Ok(pl) = PolyLine::new(road.osm_center_points.clone()) {
|
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
|
// 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 {
|
for pt in barrier_nodes {
|
||||||
// Many barriers are on footpaths or roads that we don't retain
|
// 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
|
// Filters on roads that're already car-free are redundant
|
||||||
if road.is_driveable() {
|
if road.is_driveable() {
|
||||||
road.barrier_nodes.push(pt.to_pt2d());
|
road.barrier_nodes.push(pt.to_pt2d());
|
||||||
@ -292,12 +300,15 @@ fn bristol_hack(map: &mut RawMap) {
|
|||||||
tags.insert("maxspeed", "1 mph");
|
tags.insert("maxspeed", "1 mph");
|
||||||
tags.insert("bicycle", "no");
|
tags.insert("bicycle", "no");
|
||||||
|
|
||||||
map.roads.insert(
|
map.streets.roads.insert(
|
||||||
id,
|
id,
|
||||||
RawRoad::new(
|
RawRoad::new(
|
||||||
vec![map.intersections[&i1].point, map.intersections[&i2].point],
|
vec![
|
||||||
|
map.streets.intersections[&i1].point,
|
||||||
|
map.streets.intersections[&i2].point,
|
||||||
|
],
|
||||||
tags,
|
tags,
|
||||||
&map.config,
|
&map.streets.config,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ pub fn apply_parking(map: &mut RawMap, opts: &Options, timer: &mut Timer) {
|
|||||||
}
|
}
|
||||||
OnstreetParking::SomeAdditionalWhereNoData { pct } => {
|
OnstreetParking::SomeAdditionalWhereNoData { pct } => {
|
||||||
let pct = pct as i64;
|
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.
|
// The 20m minimum is a rough heuristic.
|
||||||
if r.osm_tags.contains_key(osm::INFERRED_PARKING)
|
if r.osm_tags.contains_key(osm::INFERRED_PARKING)
|
||||||
&& r.osm_tags
|
&& 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)
|
// Match shapes with the nearest road + direction (true for forwards)
|
||||||
let mut closest: FindClosest<(OriginalRoad, bool)> =
|
let mut closest: FindClosest<(OriginalRoad, bool)> =
|
||||||
FindClosest::new(&map.gps_bounds.to_bounds());
|
FindClosest::new(&map.streets.gps_bounds.to_bounds());
|
||||||
for (id, r) in &map.roads {
|
for (id, r) in &map.streets.roads {
|
||||||
if r.is_light_rail() || r.is_footway() || r.is_service() {
|
if r.is_light_rail() || r.is_footway() || r.is_service() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for s in shapes.shapes.into_iter() {
|
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 {
|
if pts.len() <= 1 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(((r, fwds), _)) = closest.closest_pt(middle, DIRECTED_ROAD_THICKNESS * 5.0) {
|
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.
|
// Skip if the road already has this mapped.
|
||||||
if !tags.contains_key(osm::INFERRED_PARKING) {
|
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);
|
tags.insert(osm::PARKING_BOTH, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let lane_specs_ltr = raw_map::get_lane_specs_ltr(tags, &map.config);
|
let lane_specs_ltr = raw_map::get_lane_specs_ltr(tags, &map.streets.config);
|
||||||
map.roads.get_mut(&r).unwrap().lane_specs_ltr = lane_specs_ltr;
|
map.streets.roads.get_mut(&r).unwrap().lane_specs_ltr = lane_specs_ltr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.stop("apply parking hints");
|
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");
|
timer.start("match offstreet parking points");
|
||||||
let shapes: ExtraShapes = abstio::read_binary(path, timer);
|
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 {
|
for (id, b) in &map.buildings {
|
||||||
closest.add(*id, b.polygon.points());
|
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.
|
// TODO Another function just to use ?. Try blocks would rock.
|
||||||
let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
|
let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
|
||||||
assert_eq!(s.points.len(), 1);
|
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))?;
|
let (id, _) = closest.closest_pt(pt, Distance::meters(50.0))?;
|
||||||
// TODO Handle parking lots.
|
// TODO Handle parking lots.
|
||||||
if !map.buildings[&id].polygon.contains_pt(pt) {
|
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 {
|
for (pt, id) in &pt_to_intersection {
|
||||||
map.intersections.insert(
|
map.streets.intersections.insert(
|
||||||
*id,
|
*id,
|
||||||
RawIntersection::new(
|
RawIntersection::new(
|
||||||
pt.to_pt2d(),
|
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
|
// Set roundabouts to their center
|
||||||
for (id, point) in roundabout_centers {
|
for (id, point) in roundabout_centers {
|
||||||
map.intersections
|
map.streets
|
||||||
|
.intersections
|
||||||
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
|
.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));
|
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) => {
|
Ok(road) => {
|
||||||
map.roads.insert(id, road);
|
map.streets.roads.insert(id, road);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Skipping {id}: {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)
|
// Resolve simple turn restrictions (via a node)
|
||||||
let mut restrictions = Vec::new();
|
let mut restrictions = Vec::new();
|
||||||
for (restriction, from_osm, via_osm, to_osm) in input.simple_turn_restrictions {
|
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
|
// If some of the roads are missing, they were likely filtered out -- usually service
|
||||||
// roads.
|
// roads.
|
||||||
if let (Some(from), Some(to)) = (
|
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 {
|
for (from, rt, to) in restrictions {
|
||||||
map.roads
|
map.streets
|
||||||
|
.roads
|
||||||
.get_mut(&from)
|
.get_mut(&from)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.turn_restrictions
|
.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();
|
let mut complicated_restrictions = Vec::new();
|
||||||
for (rel_osm, from_osm, via_osm, to_osm) in input.complicated_turn_restrictions {
|
for (rel_osm, from_osm, via_osm, to_osm) in input.complicated_turn_restrictions {
|
||||||
let via_candidates: Vec<OriginalRoad> = map
|
let via_candidates: Vec<OriginalRoad> = map
|
||||||
|
.streets
|
||||||
.roads
|
.roads
|
||||||
.keys()
|
.keys()
|
||||||
.filter(|r| r.osm_way_id == via_osm)
|
.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 via = via_candidates[0];
|
||||||
|
|
||||||
let maybe_from = map
|
let maybe_from = map
|
||||||
|
.streets
|
||||||
.roads_per_intersection(via.i1)
|
.roads_per_intersection(via.i1)
|
||||||
.into_iter()
|
.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);
|
.find(|r| r.osm_way_id == from_osm);
|
||||||
let maybe_to = map
|
let maybe_to = map
|
||||||
|
.streets
|
||||||
.roads_per_intersection(via.i1)
|
.roads_per_intersection(via.i1)
|
||||||
.into_iter()
|
.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);
|
.find(|r| r.osm_way_id == to_osm);
|
||||||
match (maybe_from, maybe_to) {
|
match (maybe_from, maybe_to) {
|
||||||
(Some(from), Some(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 {
|
for (from, via, to) in complicated_restrictions {
|
||||||
map.roads
|
map.streets
|
||||||
|
.roads
|
||||||
.get_mut(&from)
|
.get_mut(&from)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.complicated_turn_restrictions
|
.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 {
|
for (pt, dir) in input.traffic_signals {
|
||||||
if let Some(r) = pt_to_road.get(&pt) {
|
if let Some(r) = pt_to_road.get(&pt) {
|
||||||
// Example: https://www.openstreetmap.org/node/26734224
|
// 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 };
|
let i = if dir == Direction::Fwd { r.i2 } else { r.i1 };
|
||||||
map.intersections.get_mut(&i).unwrap().intersection_type =
|
map.streets
|
||||||
IntersectionType::TrafficSignal;
|
.intersections
|
||||||
|
.get_mut(&i)
|
||||||
|
.unwrap()
|
||||||
|
.intersection_type = IntersectionType::TrafficSignal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ pub async fn import_extra_data(
|
|||||||
download_kml(
|
download_kml(
|
||||||
map.get_city_name().input_path("planning_areas.bin"),
|
map.get_city_name().input_path("planning_areas.bin"),
|
||||||
"https://tsb-opendata.s3.eu-central-1.amazonaws.com/lor_planungsgraeume/lor_planungsraeume.kml",
|
"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
|
// Keep partly out-of-bounds polygons
|
||||||
false,
|
false,
|
||||||
timer
|
timer
|
||||||
|
@ -32,6 +32,20 @@ pub fn config_for_map(name: &MapName) -> convert_osm::Options {
|
|||||||
Distance::meters(8.0)
|
Distance::meters(8.0)
|
||||||
},
|
},
|
||||||
turn_on_red: name.city.country == "us" && name.city.city != "nyc",
|
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() {
|
onstreet_parking: match name.city.city.as_ref() {
|
||||||
"seattle" => {
|
"seattle" => {
|
||||||
|
@ -30,7 +30,7 @@ pub async fn import_collision_data(
|
|||||||
// Always do this, it's idempotent and fast
|
// Always do this, it's idempotent and fast
|
||||||
let shapes = kml::ExtraShapes::load_csv(
|
let shapes = kml::ExtraShapes::load_csv(
|
||||||
path_shared_input("Road Safety Data - Accidents 2019.csv"),
|
path_shared_input("Road Safety Data - Accidents 2019.csv"),
|
||||||
&map.gps_bounds,
|
&map.streets.gps_bounds,
|
||||||
timer,
|
timer,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -45,12 +45,13 @@ pub struct RawToMapOptions {
|
|||||||
|
|
||||||
impl Map {
|
impl Map {
|
||||||
pub fn create_from_raw(mut raw: RawMap, opts: RawToMapOptions, timer: &mut Timer) -> 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");
|
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 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");
|
timer.stop("raw_map to InitialMap");
|
||||||
|
|
||||||
let mut map = Map {
|
let mut map = Map {
|
||||||
@ -62,12 +63,12 @@ impl Map {
|
|||||||
areas: Vec::new(),
|
areas: Vec::new(),
|
||||||
parking_lots: Vec::new(),
|
parking_lots: Vec::new(),
|
||||||
zones: Vec::new(),
|
zones: Vec::new(),
|
||||||
boundary_polygon: raw.boundary_polygon.clone(),
|
boundary_polygon: raw.streets.boundary_polygon.clone(),
|
||||||
stop_signs: BTreeMap::new(),
|
stop_signs: BTreeMap::new(),
|
||||||
traffic_signals: BTreeMap::new(),
|
traffic_signals: BTreeMap::new(),
|
||||||
gps_bounds,
|
gps_bounds,
|
||||||
bounds,
|
bounds,
|
||||||
config: raw.config.clone(),
|
config: raw.streets.config.clone(),
|
||||||
pathfinder: Pathfinder::empty(),
|
pathfinder: Pathfinder::empty(),
|
||||||
pathfinder_dirty: false,
|
pathfinder_dirty: false,
|
||||||
routing_params: RoutingParams::default(),
|
routing_params: RoutingParams::default(),
|
||||||
@ -99,7 +100,9 @@ impl Map {
|
|||||||
incoming_lanes: Vec::new(),
|
incoming_lanes: Vec::new(),
|
||||||
outgoing_lanes: Vec::new(),
|
outgoing_lanes: Vec::new(),
|
||||||
roads: i.roads.iter().map(|id| road_id_mapping[id]).collect(),
|
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);
|
intersection_id_mapping.insert(i.id, id);
|
||||||
}
|
}
|
||||||
@ -112,7 +115,7 @@ impl Map {
|
|||||||
let i1 = intersection_id_mapping[&r.src_i];
|
let i1 = intersection_id_mapping[&r.src_i];
|
||||||
let i2 = intersection_id_mapping[&r.dst_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 barrier_nodes = snap_nodes_to_line(&raw_road.barrier_nodes, &r.trimmed_center_pts);
|
||||||
let mut road = Road {
|
let mut road = Road {
|
||||||
id: road_id,
|
id: road_id,
|
||||||
|
@ -152,6 +152,7 @@ impl Map {
|
|||||||
inferred_sidewalks: true,
|
inferred_sidewalks: true,
|
||||||
street_parking_spot_length: Distance::meters(8.0),
|
street_parking_spot_length: Distance::meters(8.0),
|
||||||
turn_on_red: true,
|
turn_on_red: true,
|
||||||
|
find_dog_legs_experiment: false,
|
||||||
},
|
},
|
||||||
pathfinder: Pathfinder::empty(),
|
pathfinder: Pathfinder::empty(),
|
||||||
pathfinder_dirty: false,
|
pathfinder_dirty: false,
|
||||||
|
@ -5,16 +5,10 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aabb-quadtree = "0.1.0"
|
|
||||||
abstio = { path = "../abstio" }
|
abstio = { path = "../abstio" }
|
||||||
abstutil = { path = "../abstutil" }
|
abstutil = { path = "../abstutil" }
|
||||||
anyhow = "1.0.38"
|
|
||||||
geojson = { version = "0.22.2", features = ["geo-types"] }
|
|
||||||
geom = { path = "../geom" }
|
geom = { path = "../geom" }
|
||||||
kml = { path = "../kml" }
|
|
||||||
log = "0.4.14"
|
|
||||||
petgraph = { version = "0.6.0" }
|
|
||||||
serde = "1.0.123"
|
serde = "1.0.123"
|
||||||
serde_json = "1.0.61"
|
street_network = { path = "../street_network" }
|
||||||
strum = "0.21"
|
strum = "0.21"
|
||||||
strum_macros = "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
|
//! 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.
|
//! 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::collections::BTreeMap;
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use petgraph::graphmap::DiGraphMap;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use abstio::{CityName, MapName};
|
use abstio::{CityName, MapName};
|
||||||
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
|
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};
|
// Re-export everything for refactoring convenience
|
||||||
pub use self::lane_specs::get_lane_specs_ltr;
|
pub use street_network::*;
|
||||||
pub use self::types::{
|
|
||||||
Amenity, AmenityType, AreaType, BufferType, Direction, DrivingSide, IntersectionType, LaneSpec,
|
pub use self::types::{Amenity, AmenityType, AreaType};
|
||||||
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;
|
mod types;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct RawMap {
|
pub struct RawMap {
|
||||||
pub name: MapName,
|
pub name: MapName,
|
||||||
#[serde(
|
pub streets: StreetNetwork,
|
||||||
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>,
|
|
||||||
#[serde(
|
#[serde(
|
||||||
serialize_with = "serialize_btreemap",
|
serialize_with = "serialize_btreemap",
|
||||||
deserialize_with = "deserialize_btreemap"
|
deserialize_with = "deserialize_btreemap"
|
||||||
@ -61,127 +35,19 @@ pub struct RawMap {
|
|||||||
deserialize_with = "deserialize_btreemap"
|
deserialize_with = "deserialize_btreemap"
|
||||||
)]
|
)]
|
||||||
pub transit_stops: BTreeMap<String, RawTransitStop>,
|
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 {
|
impl RawMap {
|
||||||
pub fn blank(name: MapName) -> RawMap {
|
pub fn blank(name: MapName) -> RawMap {
|
||||||
RawMap {
|
RawMap {
|
||||||
name,
|
name,
|
||||||
roads: BTreeMap::new(),
|
streets: StreetNetwork::blank(),
|
||||||
intersections: BTreeMap::new(),
|
|
||||||
buildings: BTreeMap::new(),
|
buildings: BTreeMap::new(),
|
||||||
areas: Vec::new(),
|
areas: Vec::new(),
|
||||||
parking_lots: Vec::new(),
|
parking_lots: Vec::new(),
|
||||||
parking_aisles: Vec::new(),
|
parking_aisles: Vec::new(),
|
||||||
transit_routes: Vec::new(),
|
transit_routes: Vec::new(),
|
||||||
transit_stops: BTreeMap::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 {
|
loop {
|
||||||
let candidate = osm::WayID(osm_way_id);
|
let candidate = osm::WayID(osm_way_id);
|
||||||
// TODO Doesn't handle collisions with areas or parking lots
|
// 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
|
|| self
|
||||||
.buildings
|
.buildings
|
||||||
.keys()
|
.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) {
|
pub fn save(&self) {
|
||||||
abstio::write_binary(abstio::path_raw_map(&self.name), 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)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct RawBuilding {
|
pub struct RawBuilding {
|
||||||
pub polygon: Polygon,
|
pub polygon: Polygon,
|
||||||
@ -559,33 +109,6 @@ pub struct RawParkingLot {
|
|||||||
pub osm_tags: Tags,
|
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)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct RawTransitRoute {
|
pub struct RawTransitRoute {
|
||||||
pub long_name: String,
|
pub long_name: String,
|
||||||
|
@ -1,19 +1,9 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::{Display, EnumIter, EnumString};
|
use strum_macros::{Display, EnumIter, EnumString};
|
||||||
|
|
||||||
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
|
use abstutil::Tags;
|
||||||
use geom::Distance;
|
use street_network::NamePerLanguage;
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
/// A business located inside a building.
|
/// A business located inside a building.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[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)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum AreaType {
|
pub enum AreaType {
|
||||||
Park,
|
Park,
|
||||||
@ -244,343 +191,3 @@ pub enum AreaType {
|
|||||||
/// Not from OSM. A user-specified area to focus on.
|
/// Not from OSM. A user-specified area to focus on.
|
||||||
StudyArea,
|
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 abstutil::Timer;
|
||||||
|
|
||||||
use crate::RawMap;
|
use crate::StreetNetwork;
|
||||||
|
|
||||||
impl RawMap {
|
impl StreetNetwork {
|
||||||
/// Assumes `run_all_simplifications` has been called if desired
|
/// Assumes `run_all_simplifications` has been called if desired
|
||||||
pub fn save_to_geojson(&self, output_path: String, timer: &mut Timer) -> Result<()> {
|
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
|
// 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>.
|
//! for the step of producing <https://a-b-street.github.io/docs/tech/map/importing/geometry.html>.
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
@ -6,7 +6,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||||||
use abstutil::{Tags, Timer};
|
use abstutil::{Tags, Timer};
|
||||||
use geom::{Bounds, Circle, Distance, PolyLine, Polygon, Pt2D};
|
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 struct InitialMap {
|
||||||
pub roads: BTreeMap<OriginalRoad, Road>,
|
pub roads: BTreeMap<OriginalRoad, Road>,
|
||||||
@ -28,7 +28,7 @@ pub struct Road {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 road = &map.roads[&id];
|
||||||
let (trimmed_center_pts, total_width) = road.untrimmed_road_geometry();
|
let (trimmed_center_pts, total_width) = road.untrimmed_road_geometry();
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ pub struct Intersection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InitialMap {
|
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 {
|
let mut m = InitialMap {
|
||||||
roads: BTreeMap::new(),
|
roads: BTreeMap::new(),
|
||||||
intersections: 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 geom::{Distance, Pt2D};
|
||||||
|
|
||||||
use crate::osm::NodeID;
|
use crate::osm::NodeID;
|
||||||
use crate::{osm, IntersectionType, OriginalRoad, RawMap};
|
use crate::{osm, IntersectionType, OriginalRoad, StreetNetwork};
|
||||||
|
|
||||||
/// Collapse degenerate intersections:
|
/// Collapse degenerate intersections:
|
||||||
/// - between two cycleways
|
/// - between two cycleways
|
||||||
/// - when the lane specs match and only "unimportant" OSM tags differ
|
/// - 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();
|
let mut merge: Vec<NodeID> = Vec::new();
|
||||||
for id in raw.intersections.keys() {
|
for id in raw.intersections.keys() {
|
||||||
let roads = raw.roads_per_intersection(*id);
|
let roads = raw.roads_per_intersection(*id);
|
||||||
@ -35,7 +35,7 @@ pub fn collapse(raw: &mut RawMap) {
|
|||||||
// Results look good so far.
|
// 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 road1 = &raw.roads[&r1];
|
||||||
let road2 = &raw.roads[&r2];
|
let road2 = &raw.roads[&r2];
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &RawMap) -> Result<(
|
|||||||
Ok(())
|
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);
|
let roads = raw.roads_per_intersection(i);
|
||||||
assert_eq!(roads.len(), 2);
|
assert_eq!(roads.len(), 2);
|
||||||
let mut r1 = roads[0];
|
let mut r1 = roads[0];
|
||||||
@ -226,7 +226,7 @@ const SHORT_THRESHOLD: Distance = Distance::const_meters(30.0);
|
|||||||
/// "stubs." Trim those.
|
/// "stubs." Trim those.
|
||||||
///
|
///
|
||||||
/// Also do the same thing for extremely short dead-end service roads.
|
/// 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_roads = BTreeSet::new();
|
||||||
let mut remove_intersections = BTreeSet::new();
|
let mut remove_intersections = BTreeSet::new();
|
||||||
for (id, i) in &raw.intersections {
|
for (id, i) in &raw.intersections {
|
@ -1,12 +1,12 @@
|
|||||||
use std::collections::BTreeSet;
|
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
|
/// 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
|
/// (https://wiki.openstreetmap.org/wiki/Tag:dual_carriageway%3Dyes), but we can also apply simple
|
||||||
/// heuristics to guess this.
|
/// heuristics to guess this.
|
||||||
#[allow(unused)]
|
#[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();
|
let connectors_angle = map.roads[id].angle();
|
||||||
// There are false positives like https://www.openstreetmap.org/way/4636259 when we're looking
|
// 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
|
// 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 abstutil::Timer;
|
||||||
use geom::Distance;
|
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
|
/// Combines a few different sources/methods to decide which roads are short. Marks them for
|
||||||
/// merging.
|
/// merging.
|
||||||
@ -10,7 +9,7 @@ use crate::{osm, IntersectionType, OriginalRoad, RawMap};
|
|||||||
/// 1) Anything tagged in OSM
|
/// 1) Anything tagged in OSM
|
||||||
/// 2) Anything a temporary local merge_osm_ways.json file
|
/// 2) Anything a temporary local merge_osm_ways.json file
|
||||||
/// 3) If `consolidate_all` is true, an experimental distance-based heuristic
|
/// 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();
|
let mut roads = Vec::new();
|
||||||
for (id, road) in &map.roads {
|
for (id, road) in &map.roads {
|
||||||
if road.osm_tags.is("junction", "intersection") {
|
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
|
// Gradually rolling out
|
||||||
if vec![
|
if map.config.find_dog_legs_experiment {
|
||||||
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)
|
|
||||||
{
|
|
||||||
roads.extend(map.find_dog_legs());
|
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)
|
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) {
|
let road_length = if let Ok(pl) = map.trimmed_road_geometry(id) {
|
||||||
pl.length()
|
pl.length()
|
||||||
} else {
|
} else {
|
||||||
@ -67,7 +52,7 @@ fn distance_heuristic(id: OriginalRoad, map: &RawMap) -> bool {
|
|||||||
road_length < Distance::meters(5.0)
|
road_length < Distance::meters(5.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawMap {
|
impl StreetNetwork {
|
||||||
fn mark_short_roads(&mut self, list: Vec<OriginalRoad>) -> Vec<OriginalRoad> {
|
fn mark_short_roads(&mut self, list: Vec<OriginalRoad>) -> Vec<OriginalRoad> {
|
||||||
for id in &list {
|
for id in &list {
|
||||||
self.roads
|
self.roads
|
||||||
@ -172,7 +157,7 @@ impl RawMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Dedupe with find_divided_highways logic in parking_mapper
|
// 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);
|
assert_eq!(roads.len(), 3);
|
||||||
// Look for one-way roads with the same name
|
// Look for one-way roads with the same name
|
||||||
for (r1, r2) in [
|
for (r1, r2) in [
|
@ -2,13 +2,13 @@ use std::collections::{BTreeMap, VecDeque};
|
|||||||
|
|
||||||
use anyhow::Result;
|
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
|
// 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
|
// bet the underlying problem there would help debug automated transformations near merged roads
|
||||||
// too.
|
// too.
|
||||||
|
|
||||||
impl RawMap {
|
impl StreetNetwork {
|
||||||
/// Returns (the surviving intersection, the deleted intersection, deleted roads, new roads)
|
/// Returns (the surviving intersection, the deleted intersection, deleted roads, new roads)
|
||||||
pub fn merge_short_road(
|
pub fn merge_short_road(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -192,7 +192,7 @@ impl RawMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Merge all roads marked with `junction=intersection`
|
/// 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();
|
let mut queue: VecDeque<OriginalRoad> = VecDeque::new();
|
||||||
for (id, road) in &map.roads {
|
for (id, road) in &map.roads {
|
||||||
if road.osm_tags.is("junction", "intersection") {
|
if road.osm_tags.is("junction", "intersection") {
|
@ -1,6 +1,6 @@
|
|||||||
use abstutil::Timer;
|
use abstutil::Timer;
|
||||||
|
|
||||||
use crate::RawMap;
|
use crate::StreetNetwork;
|
||||||
|
|
||||||
mod collapse_intersections;
|
mod collapse_intersections;
|
||||||
mod dual_carriageways;
|
mod dual_carriageways;
|
||||||
@ -10,20 +10,20 @@ mod remove_disconnected;
|
|||||||
mod shrink_roads;
|
mod shrink_roads;
|
||||||
mod snappy;
|
mod snappy;
|
||||||
|
|
||||||
impl RawMap {
|
impl StreetNetwork {
|
||||||
// TODO I suspect we'll soon take a full struct of options, maybe even a list of transformation
|
// TODO I suspect we'll soon take a full struct of options, maybe even a list of transformation
|
||||||
// enums to run in order
|
// 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
|
/// 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 RawMap.
|
/// map_editor tool to debug the StreetNetwork.
|
||||||
pub fn run_all_simplifications(
|
pub fn run_all_simplifications(
|
||||||
&mut self,
|
&mut self,
|
||||||
consolidate_all_intersections: bool,
|
consolidate_all_intersections: bool,
|
||||||
remove_disconnected: bool,
|
remove_disconnected: bool,
|
||||||
timer: &mut Timer,
|
timer: &mut Timer,
|
||||||
) {
|
) {
|
||||||
timer.start("simplify RawMap");
|
timer.start("simplify StreetNetwork");
|
||||||
|
|
||||||
timer.start("trimming dead-end cycleways (round 1)");
|
timer.start("trimming dead-end cycleways (round 1)");
|
||||||
collapse_intersections::trim_deadends(self);
|
collapse_intersections::trim_deadends(self);
|
||||||
@ -56,6 +56,6 @@ impl RawMap {
|
|||||||
shrink_roads::shrink(self, timer);
|
shrink_roads::shrink(self, timer);
|
||||||
timer.stop("shrinking overlapping roads");
|
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 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
|
/// 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.
|
/// 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");
|
timer.start("removing disconnected roads");
|
||||||
// This is a simple floodfill, not Tarjan's. Assumes all roads bidirectional.
|
// This is a simple floodfill, not Tarjan's. Assumes all roads bidirectional.
|
||||||
// All the usizes are indices into the original list of roads
|
// 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 aabb_quadtree::QuadTree;
|
||||||
use abstutil::Timer;
|
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
|
/// Look for roads that physically overlap, but aren't connected by an intersection. Shrink their
|
||||||
/// width.
|
/// 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_centers = HashMap::new();
|
||||||
let mut road_polygons = HashMap::new();
|
let mut road_polygons = HashMap::new();
|
||||||
let mut overlapping = Vec::new();
|
let mut overlapping = Vec::new();
|
@ -4,12 +4,12 @@ use abstutil::MultiMap;
|
|||||||
use geom::{Distance, FindClosest, Line, PolyLine};
|
use geom::{Distance, FindClosest, Line, PolyLine};
|
||||||
use kml::{ExtraShape, ExtraShapes};
|
use kml::{ExtraShape, ExtraShapes};
|
||||||
|
|
||||||
use crate::{Direction, OriginalRoad, RawMap};
|
use crate::{Direction, OriginalRoad, StreetNetwork};
|
||||||
|
|
||||||
const DEBUG_OUTPUT: bool = true;
|
const DEBUG_OUTPUT: bool = true;
|
||||||
|
|
||||||
/// Snap separately mapped cycleways to main roads.
|
/// Snap separately mapped cycleways to main roads.
|
||||||
pub fn snap_cycleways(map: &mut RawMap) {
|
pub fn snap_cycleways(map: &mut StreetNetwork) {
|
||||||
if true {
|
if true {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ struct Cycleway {
|
|||||||
// cycleways hit.
|
// cycleways hit.
|
||||||
// TODO Or look for cycleway polygons strictly overlapping thick road polygons
|
// TODO Or look for cycleway polygons strictly overlapping thick road polygons
|
||||||
fn v1(
|
fn v1(
|
||||||
map: &RawMap,
|
map: &StreetNetwork,
|
||||||
cycleways: &[Cycleway],
|
cycleways: &[Cycleway],
|
||||||
road_edges: &HashMap<(OriginalRoad, Direction), PolyLine>,
|
road_edges: &HashMap<(OriginalRoad, Direction), PolyLine>,
|
||||||
) -> MultiMap<OriginalRoad, (OriginalRoad, Direction)> {
|
) -> 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(
|
abstio::write_binary(
|
||||||
map.name
|
map.name
|
||||||
.city
|
.city
|
||||||
.input_path(format!("{}_snapping.bin", map.name.map)),
|
.input_path(format!("{}_snapping.bin", map.name.map)),
|
||||||
&ExtraShapes {
|
&debug_shapes
|
||||||
shapes: debug_shapes,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
matches
|
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