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:
Dustin Carlino 2022-07-06 13:26:27 +01:00
parent c95c16454b
commit 9980cf0a35
42 changed files with 1246 additions and 1095 deletions

25
Cargo.lock generated
View File

@ -3155,17 +3155,11 @@ dependencies = [
name = "raw_map"
version = "0.1.0"
dependencies = [
"aabb-quadtree",
"abstio",
"abstutil",
"anyhow",
"geojson",
"geom",
"kml",
"log",
"petgraph",
"serde",
"serde_json",
"street_network",
"strum",
"strum_macros",
]
@ -3711,6 +3705,23 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]]
name = "street_network"
version = "0.1.0"
dependencies = [
"aabb-quadtree",
"abstio",
"abstutil",
"anyhow",
"geojson",
"geom",
"kml",
"log",
"petgraph",
"serde",
"serde_json",
]
[[package]]
name = "strsim"
version = "0.8.0"

View File

@ -17,6 +17,7 @@ members = [
"popdat",
"raw_map",
"sim",
"street_network",
"synthpop",
"tests",
"traffic_seitan",

View File

@ -54,7 +54,7 @@ impl MainState {
.to_string(),
);
}
let bounds = app.model.map.gps_bounds.to_bounds();
let bounds = app.model.map.streets.gps_bounds.to_bounds();
ctx.canvas.map_dims = (bounds.width(), bounds.height());
let mut state = MainState {
@ -263,14 +263,17 @@ impl State<App> for MainState {
self.mode = Mode::SetBoundaryPt1;
}
"detect short roads" => {
for r in app.model.map.find_dog_legs() {
for r in app.model.map.streets.find_dog_legs() {
app.model.road_deleted(r);
app.model.road_added(ctx, r);
}
}
"simplify RawMap" => {
ctx.loading_screen("simplify", |ctx, timer| {
app.model.map.run_all_simplifications(false, true, timer);
app.model
.map
.streets
.run_all_simplifications(false, true, timer);
app.model.recreate_world(ctx, timer);
});
}
@ -306,7 +309,7 @@ impl State<App> for MainState {
}
Mode::CreatingRoad(i1) => {
if ctx.canvas_movement() {
URLManager::update_url_cam(ctx, &app.model.map.gps_bounds);
URLManager::update_url_cam(ctx, &app.model.map.streets.gps_bounds);
}
if ctx.input.pressed(Key::Escape) {
@ -322,7 +325,7 @@ impl State<App> for MainState {
}
Mode::SetBoundaryPt1 => {
if ctx.canvas_movement() {
URLManager::update_url_cam(ctx, &app.model.map.gps_bounds);
URLManager::update_url_cam(ctx, &app.model.map.streets.gps_bounds);
}
let mut txt = Text::new();
@ -341,7 +344,7 @@ impl State<App> for MainState {
}
Mode::SetBoundaryPt2(pt1) => {
if ctx.canvas_movement() {
URLManager::update_url_cam(ctx, &app.model.map.gps_bounds);
URLManager::update_url_cam(ctx, &app.model.map.streets.gps_bounds);
}
let mut txt = Text::new();
@ -371,7 +374,7 @@ impl State<App> for MainState {
g.draw_polygon(
Color::rgb(242, 239, 233),
app.model.map.boundary_polygon.clone(),
app.model.map.streets.boundary_polygon.clone(),
);
app.model.world.draw(g);
g.redraw(&app.model.draw_extra);
@ -380,7 +383,8 @@ impl State<App> for MainState {
Mode::Neutral | Mode::SetBoundaryPt1 => {}
Mode::CreatingRoad(i1) => {
if let Some(cursor) = g.get_cursor_in_map_space() {
if let Ok(l) = Line::new(app.model.map.intersections[&i1].point, cursor) {
if let Ok(l) = Line::new(app.model.map.streets.intersections[&i1].point, cursor)
{
g.draw_polygon(Color::GREEN, l.make_polygons(Distance::meters(5.0)));
}
}

View File

@ -14,10 +14,10 @@ pub struct EditRoad {
impl EditRoad {
pub(crate) fn new_state(ctx: &mut EventCtx, app: &App, r: OriginalRoad) -> Box<dyn State<App>> {
let road = &app.model.map.roads[&r];
let road = &app.model.map.streets.roads[&r];
let mut batch = GeomBatch::new();
if let Ok(pl) = app.model.map.trimmed_road_geometry(r) {
if let Ok(pl) = app.model.map.streets.trimmed_road_geometry(r) {
batch.push(
Color::BLACK,
pl.make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
@ -32,7 +32,7 @@ impl EditRoad {
"Length before trimming: {}",
road.untrimmed_road_geometry().0.length()
)));
if let Ok(pl) = app.model.map.trimmed_road_geometry(r) {
if let Ok(pl) = app.model.map.streets.trimmed_road_geometry(r) {
txt.add_line(Line(format!("Length after trimming: {}", pl.length())));
}
for (rt, to) in &road.turn_restrictions {
@ -160,7 +160,7 @@ impl SimpleState<App> for EditRoad {
"Apply" => {
app.model.road_deleted(self.r);
let road = app.model.map.roads.get_mut(&self.r).unwrap();
let road = app.model.map.streets.roads.get_mut(&self.r).unwrap();
road.osm_tags.remove("lanes");
road.osm_tags.remove("oneway");
@ -200,7 +200,7 @@ impl SimpleState<App> for EditRoad {
}
road.lane_specs_ltr =
raw_map::get_lane_specs_ltr(&road.osm_tags, &app.model.map.config);
raw_map::get_lane_specs_ltr(&road.osm_tags, &app.model.map.streets.config);
app.model.road_added(ctx, self.r);
Transition::Pop

View File

@ -81,7 +81,7 @@ pub fn load_map(
if !URLManager::change_camera(
ctx,
center_camera.as_ref(),
&app.model.map.gps_bounds,
&app.model.map.streets.gps_bounds,
) && !app.model.map.name.map.is_empty()
{
CameraState::load(ctx, &app.model.map.name);

View File

@ -72,14 +72,21 @@ impl Model {
}
timer.start_iter(
"fill out world with intersections",
self.map.intersections.len(),
self.map.streets.intersections.len(),
);
for id in self.map.intersections.keys().cloned().collect::<Vec<_>>() {
for id in self
.map
.streets
.intersections
.keys()
.cloned()
.collect::<Vec<_>>()
{
timer.next();
self.intersection_added(ctx, id);
}
timer.start_iter("fill out world with roads", self.map.roads.len());
for id in self.map.roads.keys().cloned().collect::<Vec<_>>() {
timer.start_iter("fill out world with roads", self.map.streets.roads.len());
for id in self.map.streets.roads.keys().cloned().collect::<Vec<_>>() {
timer.next();
self.road_added(ctx, id);
}
@ -100,10 +107,10 @@ impl Model {
for b in self.map.buildings.values_mut() {
b.polygon = b.polygon.translate(-top_left.x(), -top_left.y());
}
for i in self.map.intersections.values_mut() {
for i in self.map.streets.intersections.values_mut() {
i.point = i.point.offset(-top_left.x(), -top_left.y());
}
for r in self.map.roads.values_mut() {
for r in self.map.streets.roads.values_mut() {
for pt in &mut r.osm_center_points {
*pt = pt.offset(-top_left.x(), -top_left.y());
}
@ -111,16 +118,22 @@ impl Model {
let pt1 = Pt2D::new(0.0, 0.0);
let pt2 = bottom_right.offset(-top_left.x(), -top_left.y());
self.map.boundary_polygon = Polygon::rectangle_two_corners(pt1, pt2).unwrap();
self.map.streets.boundary_polygon = Polygon::rectangle_two_corners(pt1, pt2).unwrap();
// Make gps_bounds sane
let mut seattle_bounds = GPSBounds::new();
seattle_bounds.update(LonLat::new(-122.453224, 47.723277));
seattle_bounds.update(LonLat::new(-122.240505, 47.495342));
self.map.gps_bounds = GPSBounds::new();
self.map.gps_bounds.update(pt1.to_gps(&seattle_bounds));
self.map.gps_bounds.update(pt2.to_gps(&seattle_bounds));
self.map.streets.gps_bounds = GPSBounds::new();
self.map
.streets
.gps_bounds
.update(pt1.to_gps(&seattle_bounds));
self.map
.streets
.gps_bounds
.update(pt2.to_gps(&seattle_bounds));
self.recreate_world(ctx, &mut Timer::throwaway());
}
@ -132,10 +145,10 @@ impl Model {
bounds.update(*pt);
}
}
for i in self.map.intersections.values() {
for i in self.map.streets.intersections.values() {
bounds.update(i.point);
}
for r in self.map.roads.values() {
for r in self.map.streets.roads.values() {
for pt in &r.osm_center_points {
bounds.update(*pt);
}
@ -147,7 +160,7 @@ impl Model {
// Intersections
impl Model {
fn intersection_added(&mut self, ctx: &EventCtx, id: osm::NodeID) {
let i = &self.map.intersections[&id];
let i = &self.map.streets.intersections[&id];
let color = match i.intersection_type {
IntersectionType::TrafficSignal => Color::GREEN,
IntersectionType::StopSign => Color::RED,
@ -155,8 +168,9 @@ impl Model {
IntersectionType::Construction => Color::ORANGE,
};
let poly = if self.intersection_geom && !self.map.roads_per_intersection(id).is_empty() {
match self.map.preview_intersection(id) {
let poly =
if self.intersection_geom && !self.map.streets.roads_per_intersection(id).is_empty() {
match self.map.streets.preview_intersection(id) {
Ok((poly, _, _)) => poly,
Err(err) => {
error!("No geometry for {}: {}", id, err);
@ -183,8 +197,9 @@ impl Model {
}
pub fn create_i(&mut self, ctx: &EventCtx, point: Pt2D) {
let id = self.map.new_osm_node_id(time_to_id());
let id = self.map.streets.new_osm_node_id(time_to_id());
self.map
.streets
.intersections
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
self.intersection_added(ctx, id);
@ -192,7 +207,7 @@ impl Model {
pub fn move_i(&mut self, ctx: &EventCtx, id: osm::NodeID, point: Pt2D) {
self.world.delete_before_replacement(ID::Intersection(id));
for r in self.map.move_intersection(id, point).unwrap() {
for r in self.map.streets.move_intersection(id, point).unwrap() {
self.road_deleted(r);
self.road_added(ctx, r);
}
@ -200,18 +215,18 @@ impl Model {
}
pub fn delete_i(&mut self, id: osm::NodeID) {
if !self.map.can_delete_intersection(id) {
if !self.map.streets.can_delete_intersection(id) {
error!("Can't delete intersection used by roads");
return;
}
self.map.delete_intersection(id);
self.map.streets.delete_intersection(id);
self.world.delete(ID::Intersection(id));
}
pub fn toggle_i(&mut self, ctx: &EventCtx, id: osm::NodeID) {
self.world.delete_before_replacement(ID::Intersection(id));
let i = self.map.intersections.get_mut(&id).unwrap();
let i = self.map.streets.intersections.get_mut(&id).unwrap();
if i.intersection_type == IntersectionType::TrafficSignal {
i.intersection_type = IntersectionType::StopSign;
} else if i.intersection_type == IntersectionType::StopSign {
@ -225,8 +240,18 @@ impl Model {
self.intersection_geom = show;
ctx.loading_screen("show intersection geometry", |ctx, timer| {
timer.start_iter("intersection geometry", self.map.intersections.len());
for id in self.map.intersections.keys().cloned().collect::<Vec<_>>() {
timer.start_iter(
"intersection geometry",
self.map.streets.intersections.len(),
);
for id in self
.map
.streets
.intersections
.keys()
.cloned()
.collect::<Vec<_>>()
{
timer.next();
self.world.delete_before_replacement(ID::Intersection(id));
self.intersection_added(ctx, id);
@ -239,7 +264,7 @@ impl Model {
pub fn debug_intersection_geometry(&mut self, ctx: &EventCtx, id: osm::NodeID) {
let mut batch = GeomBatch::new();
match self.map.preview_intersection(id) {
match self.map.streets.preview_intersection(id) {
Ok((_, _, labels)) => {
for (label, polygon) in labels {
let txt_batch = Text::from(Line(label).fg(Color::CYAN))
@ -261,7 +286,7 @@ impl Model {
// Roads
impl Model {
pub fn road_added(&mut self, ctx: &EventCtx, id: OriginalRoad) {
let road = &self.map.roads[&id];
let road = &self.map.streets.roads[&id];
let (center, total_width) = road.untrimmed_road_geometry();
let hitbox = center.make_polygons(total_width);
let mut draw = GeomBatch::new();
@ -301,6 +326,7 @@ impl Model {
// Ban cul-de-sacs, since they get stripped out later anyway.
if self
.map
.streets
.roads
.keys()
.any(|r| (r.i1 == i1 && r.i2 == i2) || (r.i1 == i2 && r.i2 == i1))
@ -328,11 +354,11 @@ impl Model {
let road = match RawRoad::new(
vec![
self.map.intersections[&i1].point,
self.map.intersections[&i2].point,
self.map.streets.intersections[&i1].point,
self.map.streets.intersections[&i2].point,
],
osm_tags,
&self.map.config,
&self.map.streets.config,
) {
Ok(road) => road,
Err(err) => {
@ -344,7 +370,7 @@ impl Model {
self.world.delete_before_replacement(ID::Intersection(i1));
self.world.delete_before_replacement(ID::Intersection(i2));
self.map.roads.insert(id, road);
self.map.streets.roads.insert(id, road);
self.road_added(ctx, id);
self.intersection_added(ctx, i1);
@ -358,7 +384,7 @@ impl Model {
.delete_before_replacement(ID::Intersection(id.i1));
self.world
.delete_before_replacement(ID::Intersection(id.i2));
self.map.roads.remove(&id).unwrap();
self.map.streets.roads.remove(&id).unwrap();
self.intersection_added(ctx, id.i1);
self.intersection_added(ctx, id.i2);
@ -373,7 +399,7 @@ impl Model {
}
self.showing_pts = Some(id);
let r = &self.map.roads[&id];
let r = &self.map.streets.roads[&id];
for (idx, pt) in r.osm_center_points.iter().enumerate() {
// Don't show handles for the intersections
if idx != 0 && idx != r.osm_center_points.len() - 1 {
@ -395,7 +421,7 @@ impl Model {
return;
}
self.showing_pts = None;
for idx in 1..=self.map.roads[&id].osm_center_points.len() - 2 {
for idx in 1..=self.map.streets.roads[&id].osm_center_points.len() - 2 {
self.world.delete(ID::RoadPoint(id, idx));
}
}
@ -404,7 +430,7 @@ impl Model {
assert_eq!(self.showing_pts, Some(id));
// stop_showing_pts deletes the points, but we want to use delete_before_replacement
self.showing_pts = None;
for idx in 1..=self.map.roads[&id].osm_center_points.len() - 2 {
for idx in 1..=self.map.streets.roads[&id].osm_center_points.len() - 2 {
self.world.delete_before_replacement(ID::RoadPoint(id, idx));
}
@ -414,7 +440,13 @@ impl Model {
self.world
.delete_before_replacement(ID::Intersection(id.i2));
let pts = &mut self.map.roads.get_mut(&id).unwrap().osm_center_points;
let pts = &mut self
.map
.streets
.roads
.get_mut(&id)
.unwrap()
.osm_center_points;
pts[idx] = point;
self.road_added(ctx, id);
@ -438,7 +470,13 @@ impl Model {
self.world
.delete_before_replacement(ID::Intersection(id.i2));
let pts = &mut self.map.roads.get_mut(&id).unwrap().osm_center_points;
let pts = &mut self
.map
.streets
.roads
.get_mut(&id)
.unwrap()
.osm_center_points;
transform(pts);
self.road_added(ctx, id);
@ -477,7 +515,7 @@ impl Model {
self.stop_showing_pts(id);
let (retained_i, deleted_i, deleted_roads, created_roads) =
match self.map.merge_short_road(id) {
match self.map.streets.merge_short_road(id) {
Ok((retained_i, deleted_i, deleted_roads, created_roads)) => {
(retained_i, deleted_i, deleted_roads, created_roads)
}
@ -507,7 +545,7 @@ impl Model {
pub fn toggle_junction(&mut self, ctx: &EventCtx, id: OriginalRoad) {
self.road_deleted(id);
let road = self.map.roads.get_mut(&id).unwrap();
let road = self.map.streets.roads.get_mut(&id).unwrap();
if road.osm_tags.is("junction", "intersection") {
road.osm_tags.remove("junction");
} else {
@ -592,14 +630,14 @@ fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
f,
r#"<!-- If you couldn't tell, this is a fake .osm file not representing the real world. -->"#
)?;
let b = &map.gps_bounds;
let b = &map.streets.gps_bounds;
writeln!(
f,
r#" <bounds minlon="{}" maxlon="{}" minlat="{}" maxlat="{}"/>"#,
b.min_lon, b.max_lon, b.min_lat, b.max_lat
)?;
let mut pt_to_id: HashMap<HashablePt2D, osm::NodeID> = HashMap::new();
for (id, i) in &map.intersections {
for (id, i) in &map.streets.intersections {
pt_to_id.insert(i.point.to_hashable(), *id);
let pt = i.point.to_gps(b);
writeln!(
@ -610,7 +648,7 @@ fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
pt.y()
)?;
}
for (id, r) in &map.roads {
for (id, r) in &map.streets.roads {
writeln!(f, r#" <way id="{}">"#, id.osm_way_id.0)?;
for pt in &r.osm_center_points {
// TODO Make new IDs if needed

View File

@ -9,12 +9,12 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
timer.start("clipping map to boundary");
// So we can use retain without borrowing issues
let boundary_polygon = map.boundary_polygon.clone();
let boundary_polygon = map.streets.boundary_polygon.clone();
let boundary_ring = Ring::must_new(boundary_polygon.points().clone());
// This is kind of indirect and slow, but first pass -- just remove roads that start or end
// outside the boundary polygon.
map.roads.retain(|_, r| {
map.streets.roads.retain(|_, r| {
let first_in = boundary_polygon.contains_pt(r.osm_center_points[0]);
let last_in = boundary_polygon.contains_pt(*r.osm_center_points.last().unwrap());
let light_rail_ok = if r.is_light_rail() {
@ -33,10 +33,14 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
let mut extra_borders: BTreeMap<osm::NodeID, osm::NodeID> = BTreeMap::new();
// First pass: Clip roads beginning out of bounds
let road_ids: Vec<OriginalRoad> = map.roads.keys().cloned().collect();
let road_ids: Vec<OriginalRoad> = map.streets.roads.keys().cloned().collect();
for id in road_ids {
let r = &map.roads[&id];
if map.boundary_polygon.contains_pt(r.osm_center_points[0]) {
let r = &map.streets.roads[&id];
if map
.streets
.boundary_polygon
.contains_pt(r.osm_center_points[0])
{
continue;
}
@ -48,25 +52,26 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
// disconnects two roads in the map that would be connected if we left in some
// partly-out-of-bounds road.
if map
.streets
.roads
.keys()
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
.count()
> 1
{
let copy = map.intersections[&move_i].clone();
let copy = map.streets.intersections[&move_i].clone();
// Don't conflict with OSM IDs
move_i = map.new_osm_node_id(-1);
move_i = map.streets.new_osm_node_id(-1);
extra_borders.insert(orig_id, move_i);
map.intersections.insert(move_i, copy);
map.streets.intersections.insert(move_i, copy);
println!("Disconnecting {} from some other stuff (starting OOB)", id);
}
let i = map.intersections.get_mut(&move_i).unwrap();
let i = map.streets.intersections.get_mut(&move_i).unwrap();
i.intersection_type = IntersectionType::Border;
// Now trim it.
let mut mut_r = map.roads.remove(&id).unwrap();
let mut mut_r = map.streets.roads.remove(&id).unwrap();
let center = PolyLine::must_new(mut_r.osm_center_points.clone());
let border_pt = boundary_ring.all_intersections(&center)[0];
if let Some(pl) = center.reversed().get_slice_ending_at(border_pt) {
@ -75,7 +80,7 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
panic!("{} interacts with border strangely", id);
}
i.point = mut_r.osm_center_points[0];
map.roads.insert(
map.streets.roads.insert(
OriginalRoad {
osm_way_id: id.osm_way_id,
i1: move_i,
@ -86,10 +91,11 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
}
// Second pass: clip roads ending out of bounds
let road_ids: Vec<OriginalRoad> = map.roads.keys().cloned().collect();
let road_ids: Vec<OriginalRoad> = map.streets.roads.keys().cloned().collect();
for id in road_ids {
let r = &map.roads[&id];
let r = &map.streets.roads[&id];
if map
.streets
.boundary_polygon
.contains_pt(*r.osm_center_points.last().unwrap())
{
@ -104,24 +110,25 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
// disconnects two roads in the map that would be connected if we left in some
// partly-out-of-bounds road.
if map
.streets
.roads
.keys()
.filter(|r2| r2.i1 == move_i || r2.i2 == move_i)
.count()
> 1
{
let copy = map.intersections[&move_i].clone();
move_i = map.new_osm_node_id(-1);
let copy = map.streets.intersections[&move_i].clone();
move_i = map.streets.new_osm_node_id(-1);
extra_borders.insert(orig_id, move_i);
map.intersections.insert(move_i, copy);
map.streets.intersections.insert(move_i, copy);
println!("Disconnecting {} from some other stuff (ending OOB)", id);
}
let i = map.intersections.get_mut(&move_i).unwrap();
let i = map.streets.intersections.get_mut(&move_i).unwrap();
i.intersection_type = IntersectionType::Border;
// Now trim it.
let mut mut_r = map.roads.remove(&id).unwrap();
let mut mut_r = map.streets.roads.remove(&id).unwrap();
let center = PolyLine::must_new(mut_r.osm_center_points.clone());
let border_pt = boundary_ring.all_intersections(&center.reversed())[0];
if let Some(pl) = center.get_slice_ending_at(border_pt) {
@ -130,7 +137,7 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
panic!("{} interacts with border strangely", id);
}
i.point = *mut_r.osm_center_points.last().unwrap();
map.roads.insert(
map.streets.roads.insert(
OriginalRoad {
osm_way_id: id.osm_way_id,
i1: id.i1,
@ -149,7 +156,11 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
let mut result_areas = Vec::new();
for orig_area in map.areas.drain(..) {
for polygon in map.boundary_polygon.intersection(&orig_area.polygon) {
for polygon in map
.streets
.boundary_polygon
.intersection(&orig_area.polygon)
{
let mut area = orig_area.clone();
area.polygon = polygon;
result_areas.push(area);
@ -160,7 +171,7 @@ pub fn clip_map(map: &mut RawMap, timer: &mut Timer) {
// TODO Don't touch parking lots. It'll be visually obvious if a clip intersects one of these.
// The boundary should be manually adjusted.
if map.roads.is_empty() {
if map.streets.roads.is_empty() {
panic!("There are no roads inside the clipping polygon");
}

View File

@ -71,7 +71,7 @@ fn generate_input(input: &str, map: &RawMap) -> Result<Vec<OriginalRoad>> {
fs_err::create_dir_all(input)?;
let mut f = BufWriter::new(File::create(format!("{input}/query"))?);
let mut ids = Vec::new();
for (id, r) in &map.roads {
for (id, r) in &map.streets.roads {
// TODO Handle cul-de-sacs
if let Ok(pl) = PolyLine::new(r.osm_center_points.clone()) {
ids.push(*id);
@ -84,7 +84,13 @@ fn generate_input(input: &str, map: &RawMap) -> Result<Vec<OriginalRoad>> {
if *pts.last().unwrap() != pl.last_pt() {
pts.push(pl.last_pt());
}
for (idx, gps) in map.gps_bounds.convert_back(&pts).into_iter().enumerate() {
for (idx, gps) in map
.streets
.gps_bounds
.convert_back(&pts)
.into_iter()
.enumerate()
{
write!(f, "{},{}", gps.x(), gps.y())?;
if idx != pts.len() - 1 {
write!(f, " ")?;
@ -127,8 +133,8 @@ fn scrape_output(output: &str, map: &mut RawMap, ids: Vec<OriginalRoad>) -> Resu
continue;
}
// TODO Also put total_climb and total_descent on the roads
map.intersections.get_mut(&id.i1).unwrap().elevation = values[0];
map.intersections.get_mut(&id.i2).unwrap().elevation = values[1];
map.streets.intersections.get_mut(&id.i1).unwrap().elevation = values[0];
map.streets.intersections.get_mut(&id.i2).unwrap().elevation = values[1];
}
if cnt != num_ids {
bail!("Output had {} lines, but we made {} queries", cnt, num_ids);
@ -136,8 +142,9 @@ fn scrape_output(output: &str, map: &mut RawMap, ids: Vec<OriginalRoad>) -> Resu
// Calculate the incline for each road here, before the road gets trimmed for intersection
// geometry. If we did this after trimming, we'd miss some of the horizontal distance.
for (id, road) in &mut map.roads {
let rise = map.intersections[&id.i2].elevation - map.intersections[&id.i1].elevation;
for (id, road) in &mut map.streets.roads {
let rise = map.streets.intersections[&id.i2].elevation
- map.streets.intersections[&id.i1].elevation;
let run = road.length();
if !(rise / run).is_finite() {
// TODO Warn?

View File

@ -40,7 +40,7 @@ pub fn extract_osm(
opts: &Options,
timer: &mut Timer,
) -> OsmExtract {
let mut doc = crate::reader::read(osm_input_path, &map.gps_bounds, timer).unwrap();
let mut doc = crate::reader::read(osm_input_path, &map.streets.gps_bounds, timer).unwrap();
// TODO Hacks to override OSM data. There's no problem upstream, but we want to accomplish
// various things for A/B Street.
@ -57,8 +57,8 @@ pub fn extract_osm(
if clip_path.is_none() {
// Use the boundary from .osm.
map.gps_bounds = doc.gps_bounds.clone();
map.boundary_polygon = map.gps_bounds.to_bounds().get_rectangle();
map.streets.gps_bounds = doc.gps_bounds.clone();
map.streets.boundary_polygon = map.streets.gps_bounds.to_bounds().get_rectangle();
}
let mut out = OsmExtract {
@ -128,7 +128,7 @@ pub fn extract_osm(
.is_any(osm::HIGHWAY, vec!["cycleway", "footway", "path"])
{
extra_footways.shapes.push(ExtraShape {
points: map.gps_bounds.convert_back(&way.pts),
points: map.streets.gps_bounds.convert_back(&way.pts),
attributes: way.tags.inner().clone(),
});
} else if way.tags.is("natural", "coastline") && !way.tags.is("place", "island") {
@ -188,7 +188,7 @@ pub fn extract_osm(
abstio::write_binary(map.name.city.input_path("footways.bin"), &extra_footways);
}
let boundary = map.boundary_polygon.clone().into_ring();
let boundary = map.streets.boundary_polygon.clone().into_ring();
// TODO Fill this out in a separate loop to keep a mutable borrow short. Maybe do this in
// reader, or stop doing this entirely.
@ -576,7 +576,7 @@ fn get_area_type(tags: &Tags) -> Option<AreaType> {
// Look for any service roads that collide with parking lots, and treat them as parking aisles
// instead.
fn find_parking_aisles(map: &mut RawMap, roads: &mut Vec<(WayID, Vec<Pt2D>, Tags)>) {
let mut closest: FindClosest<usize> = FindClosest::new(&map.gps_bounds.to_bounds());
let mut closest: FindClosest<usize> = FindClosest::new(&map.streets.gps_bounds.to_bounds());
for (idx, lot) in map.parking_lots.iter().enumerate() {
closest.add(idx, lot.polygon.points());
}

View File

@ -59,7 +59,7 @@ pub fn import(map: &mut RawMap) -> Result<()> {
.deserialize()
{
let rec: Shape = rec?;
let pt = LonLat::new(rec.shape_pt_lon, rec.shape_pt_lat).to_pt(&map.gps_bounds);
let pt = LonLat::new(rec.shape_pt_lon, rec.shape_pt_lat).to_pt(&map.streets.gps_bounds);
raw_shapes
.entry(rec.shape_id)
.or_insert_with(Vec::new)
@ -146,8 +146,8 @@ pub fn import(map: &mut RawMap) -> Result<()> {
{
let rec: Stop = rec?;
if stop_ids.contains(&rec.stop_id) {
let position = LonLat::new(rec.stop_lon, rec.stop_lat).to_pt(&map.gps_bounds);
if map.boundary_polygon.contains_pt(position) {
let position = LonLat::new(rec.stop_lon, rec.stop_lat).to_pt(&map.streets.gps_bounds);
if map.streets.boundary_polygon.contains_pt(position) {
map.transit_stops.insert(
rec.stop_id.0.clone(),
RawTransitStop {
@ -234,7 +234,7 @@ fn dump_kml(map: &RawMap) {
// One polyline per route
for route in &map.transit_routes {
let points = map.gps_bounds.convert_back(route.shape.points());
let points = map.streets.gps_bounds.convert_back(route.shape.points());
let mut attributes = BTreeMap::new();
attributes.insert("long_name".to_string(), route.long_name.clone());
attributes.insert("short_name".to_string(), route.short_name.clone());
@ -249,7 +249,7 @@ fn dump_kml(map: &RawMap) {
let mut attributes = BTreeMap::new();
attributes.insert("gtfs_id".to_string(), stop.gtfs_id.clone());
attributes.insert("name".to_string(), stop.name.clone());
let points = vec![stop.position.to_gps(&map.gps_bounds)];
let points = vec![stop.position.to_gps(&map.streets.gps_bounds)];
shapes.push(ExtraShape { points, attributes });
}

View File

@ -52,6 +52,7 @@ impl Options {
inferred_sidewalks: true,
street_parking_spot_length: Distance::meters(8.0),
turn_on_red: true,
find_dog_legs_experiment: false,
},
onstreet_parking: OnstreetParking::JustOSM,
public_offstreet_parking: PublicOffstreetParking::None,
@ -111,13 +112,13 @@ pub fn convert(
) -> RawMap {
let mut map = RawMap::blank(name);
// Do this early. Calculating RawRoads uses DrivingSide, for example!
map.config = opts.map_config.clone();
map.streets.config = opts.map_config.clone();
if let Some(ref path) = clip_path {
let pts = LonLat::read_osmosis_polygon(path).unwrap();
let gps_bounds = GPSBounds::from(pts.clone());
map.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
map.gps_bounds = gps_bounds;
map.streets.boundary_polygon = Ring::must_new(gps_bounds.convert(&pts)).into_polygon();
map.streets.gps_bounds = gps_bounds;
}
let extract = extract::extract_osm(&mut map, &osm_input_path, clip_path, &opts, timer);
@ -126,7 +127,7 @@ pub fn convert(
// Need to do a first pass of removing cul-de-sacs here, or we wind up with loop PolyLines when
// doing the parking hint matching.
map.roads.retain(|r, _| r.i1 != r.i2);
map.streets.roads.retain(|r, _| r.i1 != r.i2);
use_amenities(&mut map, split_output.amenities, timer);
@ -169,7 +170,8 @@ pub fn convert(
}
fn use_amenities(map: &mut RawMap, amenities: Vec<(Pt2D, Amenity)>, timer: &mut Timer) {
let mut closest: FindClosest<osm::OsmID> = FindClosest::new(&map.gps_bounds.to_bounds());
let mut closest: FindClosest<osm::OsmID> =
FindClosest::new(&map.streets.gps_bounds.to_bounds());
for (id, b) in &map.buildings {
closest.add(*id, b.polygon.points());
}
@ -191,7 +193,7 @@ fn add_extra_buildings(map: &mut RawMap, path: &str) -> Result<()> {
let mut id = -1;
for (polygon, _) in Polygon::from_geojson_bytes(
&abstio::slurp_file(path)?,
&map.gps_bounds,
&map.streets.gps_bounds,
require_in_bounds,
)? {
// Add these as new buildings, generating a new dummy OSM ID.
@ -220,7 +222,7 @@ fn filter_crosswalks(
) {
// Normally we assume every road has a crosswalk, but since this map is configured to use OSM
// crossing nodes, let's reverse that assumption.
for road in map.roads.values_mut() {
for road in map.streets.roads.values_mut() {
road.crosswalk_forward = false;
road.crosswalk_backward = false;
}
@ -231,7 +233,10 @@ fn filter_crosswalks(
timer.next();
// Some crossing nodes are outside the map boundary or otherwise not on a road that we
// retained
if let Some(road) = pt_to_road.get(&pt).and_then(|r| map.roads.get_mut(r)) {
if let Some(road) = pt_to_road
.get(&pt)
.and_then(|r| map.streets.roads.get_mut(r))
{
// TODO Support cul-de-sacs and other loop roads
if let Ok(pl) = PolyLine::new(road.osm_center_points.clone()) {
// Crossings aren't right at an intersection. Where is this point along the center
@ -264,7 +269,10 @@ fn use_barrier_nodes(
) {
for pt in barrier_nodes {
// Many barriers are on footpaths or roads that we don't retain
if let Some(road) = pt_to_road.get(&pt).and_then(|r| map.roads.get_mut(r)) {
if let Some(road) = pt_to_road
.get(&pt)
.and_then(|r| map.streets.roads.get_mut(r))
{
// Filters on roads that're already car-free are redundant
if road.is_driveable() {
road.barrier_nodes.push(pt.to_pt2d());
@ -292,12 +300,15 @@ fn bristol_hack(map: &mut RawMap) {
tags.insert("maxspeed", "1 mph");
tags.insert("bicycle", "no");
map.roads.insert(
map.streets.roads.insert(
id,
RawRoad::new(
vec![map.intersections[&i1].point, map.intersections[&i2].point],
vec![
map.streets.intersections[&i1].point,
map.streets.intersections[&i2].point,
],
tags,
&map.config,
&map.streets.config,
)
.unwrap(),
);

View File

@ -16,7 +16,7 @@ pub fn apply_parking(map: &mut RawMap, opts: &Options, timer: &mut Timer) {
}
OnstreetParking::SomeAdditionalWhereNoData { pct } => {
let pct = pct as i64;
for (id, r) in map.roads.iter_mut() {
for (id, r) in map.streets.roads.iter_mut() {
// The 20m minimum is a rough heuristic.
if r.osm_tags.contains_key(osm::INFERRED_PARKING)
&& r.osm_tags
@ -52,8 +52,8 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
// Match shapes with the nearest road + direction (true for forwards)
let mut closest: FindClosest<(OriginalRoad, bool)> =
FindClosest::new(&map.gps_bounds.to_bounds());
for (id, r) in &map.roads {
FindClosest::new(&map.streets.gps_bounds.to_bounds());
for (id, r) in &map.streets.roads {
if r.is_light_rail() || r.is_footway() || r.is_service() {
continue;
}
@ -69,7 +69,7 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
}
for s in shapes.shapes.into_iter() {
let pts = map.gps_bounds.convert(&s.points);
let pts = map.streets.gps_bounds.convert(&s.points);
if pts.len() <= 1 {
continue;
}
@ -84,7 +84,7 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
continue;
};
if let Some(((r, fwds), _)) = closest.closest_pt(middle, DIRECTED_ROAD_THICKNESS * 5.0) {
let tags = &mut map.roads.get_mut(&r).unwrap().osm_tags;
let tags = &mut map.streets.roads.get_mut(&r).unwrap().osm_tags;
// Skip if the road already has this mapped.
if !tags.contains_key(osm::INFERRED_PARKING) {
@ -141,8 +141,8 @@ fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
tags.insert(osm::PARKING_BOTH, value);
}
let lane_specs_ltr = raw_map::get_lane_specs_ltr(tags, &map.config);
map.roads.get_mut(&r).unwrap().lane_specs_ltr = lane_specs_ltr;
let lane_specs_ltr = raw_map::get_lane_specs_ltr(tags, &map.streets.config);
map.streets.roads.get_mut(&r).unwrap().lane_specs_ltr = lane_specs_ltr;
}
}
timer.stop("apply parking hints");
@ -152,7 +152,8 @@ fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
timer.start("match offstreet parking points");
let shapes: ExtraShapes = abstio::read_binary(path, timer);
let mut closest: FindClosest<osm::OsmID> = FindClosest::new(&map.gps_bounds.to_bounds());
let mut closest: FindClosest<osm::OsmID> =
FindClosest::new(&map.streets.gps_bounds.to_bounds());
for (id, b) in &map.buildings {
closest.add(*id, b.polygon.points());
}
@ -160,7 +161,7 @@ fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
// TODO Another function just to use ?. Try blocks would rock.
let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
assert_eq!(s.points.len(), 1);
let pt = s.points[0].to_pt(&map.gps_bounds);
let pt = s.points[0].to_pt(&map.streets.gps_bounds);
let (id, _) = closest.closest_pt(pt, Distance::meters(50.0))?;
// TODO Handle parking lots.
if !map.buildings[&id].polygon.contains_pt(pt) {

View File

@ -56,7 +56,7 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
}
for (pt, id) in &pt_to_intersection {
map.intersections.insert(
map.streets.intersections.insert(
*id,
RawIntersection::new(
pt.to_pt2d(),
@ -71,7 +71,8 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
// Set roundabouts to their center
for (id, point) in roundabout_centers {
map.intersections
map.streets
.intersections
.insert(id, RawIntersection::new(point, IntersectionType::StopSign));
}
@ -113,9 +114,9 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
}
let osm_center_pts = simplify_linestring(std::mem::take(&mut pts));
match RawRoad::new(osm_center_pts, tags, &map.config) {
match RawRoad::new(osm_center_pts, tags, &map.streets.config) {
Ok(road) => {
map.roads.insert(id, road);
map.streets.roads.insert(id, road);
}
Err(err) => {
error!("Skipping {id}: {err}");
@ -136,7 +137,7 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
// Resolve simple turn restrictions (via a node)
let mut restrictions = Vec::new();
for (restriction, from_osm, via_osm, to_osm) in input.simple_turn_restrictions {
let roads = map.roads_per_intersection(via_osm);
let roads = map.streets.roads_per_intersection(via_osm);
// If some of the roads are missing, they were likely filtered out -- usually service
// roads.
if let (Some(from), Some(to)) = (
@ -147,7 +148,8 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
}
}
for (from, rt, to) in restrictions {
map.roads
map.streets
.roads
.get_mut(&from)
.unwrap()
.turn_restrictions
@ -159,6 +161,7 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
let mut complicated_restrictions = Vec::new();
for (rel_osm, from_osm, via_osm, to_osm) in input.complicated_turn_restrictions {
let via_candidates: Vec<OriginalRoad> = map
.streets
.roads
.keys()
.filter(|r| r.osm_way_id == via_osm)
@ -175,14 +178,16 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
let via = via_candidates[0];
let maybe_from = map
.streets
.roads_per_intersection(via.i1)
.into_iter()
.chain(map.roads_per_intersection(via.i2).into_iter())
.chain(map.streets.roads_per_intersection(via.i2).into_iter())
.find(|r| r.osm_way_id == from_osm);
let maybe_to = map
.streets
.roads_per_intersection(via.i1)
.into_iter()
.chain(map.roads_per_intersection(via.i2).into_iter())
.chain(map.streets.roads_per_intersection(via.i2).into_iter())
.find(|r| r.osm_way_id == to_osm);
match (maybe_from, maybe_to) {
(Some(from), Some(to)) => {
@ -197,7 +202,8 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
}
}
for (from, via, to) in complicated_restrictions {
map.roads
map.streets
.roads
.get_mut(&from)
.unwrap()
.complicated_turn_restrictions
@ -210,10 +216,16 @@ pub fn split_up_roads(map: &mut RawMap, mut input: OsmExtract, timer: &mut Timer
for (pt, dir) in input.traffic_signals {
if let Some(r) = pt_to_road.get(&pt) {
// Example: https://www.openstreetmap.org/node/26734224
if !map.roads[r].osm_tags.is(osm::HIGHWAY, "construction") {
if !map.streets.roads[r]
.osm_tags
.is(osm::HIGHWAY, "construction")
{
let i = if dir == Direction::Fwd { r.i2 } else { r.i1 };
map.intersections.get_mut(&i).unwrap().intersection_type =
IntersectionType::TrafficSignal;
map.streets
.intersections
.get_mut(&i)
.unwrap()
.intersection_type = IntersectionType::TrafficSignal;
}
}
}

View File

@ -21,7 +21,7 @@ pub async fn import_extra_data(
download_kml(
map.get_city_name().input_path("planning_areas.bin"),
"https://tsb-opendata.s3.eu-central-1.amazonaws.com/lor_planungsgraeume/lor_planungsraeume.kml",
&map.gps_bounds,
&map.streets.gps_bounds,
// Keep partly out-of-bounds polygons
false,
timer

View File

@ -32,6 +32,20 @@ pub fn config_for_map(name: &MapName) -> convert_osm::Options {
Distance::meters(8.0)
},
turn_on_red: name.city.country == "us" && name.city.city != "nyc",
find_dog_legs_experiment: vec![
MapName::seattle("montlake"),
MapName::seattle("downtown"),
MapName::seattle("lakeslice"),
MapName::new("us", "phoenix", "tempe"),
MapName::new("gb", "bristol", "east"),
//MapName::new("gb", "leeds", "north"),
//MapName::new("gb", "london", "camden"),
MapName::new("gb", "london", "kennington"),
//MapName::new("gb", "london", "southwark"),
//MapName::new("gb", "manchester", "levenshulme"),
MapName::new("pl", "krakow", "center"),
]
.contains(name),
},
onstreet_parking: match name.city.city.as_ref() {
"seattle" => {

View File

@ -30,7 +30,7 @@ pub async fn import_collision_data(
// Always do this, it's idempotent and fast
let shapes = kml::ExtraShapes::load_csv(
path_shared_input("Road Safety Data - Accidents 2019.csv"),
&map.gps_bounds,
&map.streets.gps_bounds,
timer,
)
.unwrap();

View File

@ -45,12 +45,13 @@ pub struct RawToMapOptions {
impl Map {
pub fn create_from_raw(mut raw: RawMap, opts: RawToMapOptions, timer: &mut Timer) -> Map {
raw.run_all_simplifications(opts.consolidate_all_intersections, true, timer);
raw.streets
.run_all_simplifications(opts.consolidate_all_intersections, true, timer);
timer.start("raw_map to InitialMap");
let gps_bounds = raw.gps_bounds.clone();
let gps_bounds = raw.streets.gps_bounds.clone();
let bounds = gps_bounds.to_bounds();
let initial_map = initial::InitialMap::new(&raw, &bounds, timer);
let initial_map = initial::InitialMap::new(&raw.streets, &bounds, timer);
timer.stop("raw_map to InitialMap");
let mut map = Map {
@ -62,12 +63,12 @@ impl Map {
areas: Vec::new(),
parking_lots: Vec::new(),
zones: Vec::new(),
boundary_polygon: raw.boundary_polygon.clone(),
boundary_polygon: raw.streets.boundary_polygon.clone(),
stop_signs: BTreeMap::new(),
traffic_signals: BTreeMap::new(),
gps_bounds,
bounds,
config: raw.config.clone(),
config: raw.streets.config.clone(),
pathfinder: Pathfinder::empty(),
pathfinder_dirty: false,
routing_params: RoutingParams::default(),
@ -99,7 +100,9 @@ impl Map {
incoming_lanes: Vec::new(),
outgoing_lanes: Vec::new(),
roads: i.roads.iter().map(|id| road_id_mapping[id]).collect(),
merged: !raw.intersections[&i.id].trim_roads_for_merging.is_empty(),
merged: !raw.streets.intersections[&i.id]
.trim_roads_for_merging
.is_empty(),
});
intersection_id_mapping.insert(i.id, id);
}
@ -112,7 +115,7 @@ impl Map {
let i1 = intersection_id_mapping[&r.src_i];
let i2 = intersection_id_mapping[&r.dst_i];
let raw_road = &raw.roads[&r.id];
let raw_road = &raw.streets.roads[&r.id];
let barrier_nodes = snap_nodes_to_line(&raw_road.barrier_nodes, &r.trimmed_center_pts);
let mut road = Road {
id: road_id,

View File

@ -152,6 +152,7 @@ impl Map {
inferred_sidewalks: true,
street_parking_spot_length: Distance::meters(8.0),
turn_on_red: true,
find_dog_legs_experiment: false,
},
pathfinder: Pathfinder::empty(),
pathfinder_dirty: false,

View File

@ -5,16 +5,10 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
edition = "2021"
[dependencies]
aabb-quadtree = "0.1.0"
abstio = { path = "../abstio" }
abstutil = { path = "../abstutil" }
anyhow = "1.0.38"
geojson = { version = "0.22.2", features = ["geo-types"] }
geom = { path = "../geom" }
kml = { path = "../kml" }
log = "0.4.14"
petgraph = { version = "0.6.0" }
serde = "1.0.123"
serde_json = "1.0.61"
street_network = { path = "../street_network" }
strum = "0.21"
strum_macros = "0.21"

View File

@ -2,51 +2,25 @@
//! structure is useful to iterate quickly on parts of the map importing pipeline without having to
//! constantly read .osm files, and to visualize the intermediate state with map_editor.
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate log;
use std::collections::BTreeMap;
use std::fmt;
use anyhow::Result;
use petgraph::graphmap::DiGraphMap;
use serde::{Deserialize, Serialize};
use abstio::{CityName, MapName};
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
use geom::{Angle, Distance, GPSBounds, PolyLine, Polygon, Pt2D};
use geom::{PolyLine, Polygon, Pt2D};
pub use self::geometry::{intersection_polygon, InputRoad};
pub use self::lane_specs::get_lane_specs_ltr;
pub use self::types::{
Amenity, AmenityType, AreaType, BufferType, Direction, DrivingSide, IntersectionType, LaneSpec,
LaneType, MapConfig, NamePerLanguage, NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS,
};
// Re-export everything for refactoring convenience
pub use street_network::*;
pub use self::types::{Amenity, AmenityType, AreaType};
mod edit;
mod export;
pub mod geometry;
pub mod initial;
mod lane_specs;
pub mod osm;
mod transform;
mod types;
#[derive(Debug, Serialize, Deserialize)]
pub struct RawMap {
pub name: MapName,
#[serde(
serialize_with = "serialize_btreemap",
deserialize_with = "deserialize_btreemap"
)]
pub roads: BTreeMap<OriginalRoad, RawRoad>,
#[serde(
serialize_with = "serialize_btreemap",
deserialize_with = "deserialize_btreemap"
)]
pub intersections: BTreeMap<osm::NodeID, RawIntersection>,
pub streets: StreetNetwork,
#[serde(
serialize_with = "serialize_btreemap",
deserialize_with = "deserialize_btreemap"
@ -61,127 +35,19 @@ pub struct RawMap {
deserialize_with = "deserialize_btreemap"
)]
pub transit_stops: BTreeMap<String, RawTransitStop>,
pub boundary_polygon: Polygon,
pub gps_bounds: GPSBounds,
pub config: MapConfig,
}
/// A way to refer to roads across many maps and over time. Also trivial to relate with OSM to find
/// upstream problems.
//
// - Using LonLat is more indirect, and f64's need to be trimmed and compared carefully with epsilon
// checks.
// - TODO Look at some stable ID standard like linear referencing
// (https://github.com/opentraffic/architecture/issues/1).
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OriginalRoad {
pub osm_way_id: osm::WayID,
pub i1: osm::NodeID,
pub i2: osm::NodeID,
}
impl fmt::Display for OriginalRoad {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"OriginalRoad({} from {} to {}",
self.osm_way_id, self.i1, self.i2
)
}
}
impl fmt::Debug for OriginalRoad {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl OriginalRoad {
pub fn new(way: i64, (i1, i2): (i64, i64)) -> OriginalRoad {
OriginalRoad {
osm_way_id: osm::WayID(way),
i1: osm::NodeID(i1),
i2: osm::NodeID(i2),
}
}
/// Prints the OriginalRoad in a way that can be copied to Rust code.
pub fn as_string_code(&self) -> String {
format!(
"OriginalRoad::new({}, ({}, {}))",
self.osm_way_id.0, self.i1.0, self.i2.0
)
}
pub fn has_common_endpoint(&self, other: OriginalRoad) -> bool {
if self.i1 == other.i1 || self.i1 == other.i2 {
return true;
}
if self.i2 == other.i1 || self.i2 == other.i2 {
return true;
}
false
}
// TODO Doesn't handle two roads between the same pair of intersections
pub fn common_endpt(&self, other: OriginalRoad) -> osm::NodeID {
#![allow(clippy::suspicious_operation_groupings)]
if self.i1 == other.i1 || self.i1 == other.i2 {
return self.i1;
}
if self.i2 == other.i1 || self.i2 == other.i2 {
return self.i2;
}
panic!("{:?} and {:?} have no common_endpt", self, other);
}
}
impl RawMap {
pub fn blank(name: MapName) -> RawMap {
RawMap {
name,
roads: BTreeMap::new(),
intersections: BTreeMap::new(),
streets: StreetNetwork::blank(),
buildings: BTreeMap::new(),
areas: Vec::new(),
parking_lots: Vec::new(),
parking_aisles: Vec::new(),
transit_routes: Vec::new(),
transit_stops: BTreeMap::new(),
// Some nonsense thing
boundary_polygon: Polygon::rectangle(1.0, 1.0),
gps_bounds: GPSBounds::new(),
config: MapConfig {
driving_side: DrivingSide::Right,
bikes_can_use_bus_lanes: true,
inferred_sidewalks: true,
street_parking_spot_length: Distance::meters(8.0),
turn_on_red: true,
},
}
}
// TODO Might be better to maintain this instead of doing a search everytime.
pub fn roads_per_intersection(&self, i: osm::NodeID) -> Vec<OriginalRoad> {
let mut results = Vec::new();
for id in self.roads.keys() {
if id.i1 == i || id.i2 == i {
results.push(*id);
}
}
results
}
pub fn new_osm_node_id(&self, start: i64) -> osm::NodeID {
assert!(start < 0);
// Slow, but deterministic.
let mut osm_node_id = start;
loop {
if self.intersections.keys().any(|i| i.0 == osm_node_id) {
osm_node_id -= 1;
} else {
return osm::NodeID(osm_node_id);
}
}
}
@ -193,7 +59,11 @@ impl RawMap {
loop {
let candidate = osm::WayID(osm_way_id);
// TODO Doesn't handle collisions with areas or parking lots
if self.roads.keys().any(|r| r.osm_way_id.0 == osm_way_id)
if self
.streets
.roads
.keys()
.any(|r| r.osm_way_id.0 == osm_way_id)
|| self
.buildings
.keys()
@ -206,70 +76,6 @@ impl RawMap {
}
}
/// (Intersection polygon, polygons for roads, list of labeled polygons to debug)
#[allow(clippy::type_complexity)]
pub fn preview_intersection(
&self,
id: osm::NodeID,
) -> Result<(Polygon, Vec<Polygon>, Vec<(String, Polygon)>)> {
let mut input_roads = Vec::new();
for r in self.roads_per_intersection(id) {
input_roads.push(initial::Road::new(self, r).to_input_road());
}
let results = intersection_polygon(
id,
input_roads,
// This'll be empty unless we've called merge_short_road
&self.intersections[&id].trim_roads_for_merging,
)?;
Ok((
results.intersection_polygon,
results
.trimmed_center_pts
.into_values()
.map(|(pl, half_width)| pl.make_polygons(2.0 * half_width))
.collect(),
results.debug,
))
}
/// Generate the trimmed `PolyLine` for a single RawRoad by calculating both intersections
pub fn trimmed_road_geometry(&self, road_id: OriginalRoad) -> Result<PolyLine> {
// First trim at one of the endpoints
let trimmed_center_pts = {
let mut input_roads = Vec::new();
for r in self.roads_per_intersection(road_id.i1) {
input_roads.push(initial::Road::new(self, r).to_input_road());
}
let mut results = intersection_polygon(
road_id.i1,
input_roads,
// TODO Not sure if we should use this or not
&BTreeMap::new(),
)?;
results.trimmed_center_pts.remove(&road_id).unwrap().0
};
// Now the second
{
let mut input_roads = Vec::new();
for r in self.roads_per_intersection(road_id.i2) {
let mut road = initial::Road::new(self, r).to_input_road();
if r == road_id {
road.center_pts = trimmed_center_pts.clone();
}
input_roads.push(road);
}
let mut results = intersection_polygon(
road_id.i2,
input_roads,
// TODO Not sure if we should use this or not
&BTreeMap::new(),
)?;
Ok(results.trimmed_center_pts.remove(&road_id).unwrap().0)
}
}
pub fn save(&self) {
abstio::write_binary(abstio::path_raw_map(&self.name), self)
}
@ -279,262 +85,6 @@ impl RawMap {
}
}
// Mutations and supporting queries
impl RawMap {
pub fn can_delete_intersection(&self, i: osm::NodeID) -> bool {
self.roads_per_intersection(i).is_empty()
}
pub fn delete_intersection(&mut self, id: osm::NodeID) {
if !self.can_delete_intersection(id) {
panic!(
"Can't delete_intersection {}, must have roads connected",
id
);
}
self.intersections.remove(&id).unwrap();
}
pub fn move_intersection(&mut self, id: osm::NodeID, point: Pt2D) -> Option<Vec<OriginalRoad>> {
self.intersections.get_mut(&id).unwrap().point = point;
// Update all the roads.
let mut fixed = Vec::new();
for r in self.roads_per_intersection(id) {
fixed.push(r);
let road = self.roads.get_mut(&r).unwrap();
if r.i1 == id {
road.osm_center_points[0] = point;
} else {
assert_eq!(r.i2, id);
*road.osm_center_points.last_mut().unwrap() = point;
}
}
Some(fixed)
}
pub fn closest_intersection(&self, pt: Pt2D) -> osm::NodeID {
self.intersections
.iter()
.min_by_key(|(_, i)| i.point.dist_to(pt))
.map(|(id, _)| *id)
.unwrap()
}
pub fn path_dist_to(&self, from: osm::NodeID, to: osm::NodeID) -> Option<Distance> {
let mut graph = DiGraphMap::new();
for (id, r) in &self.roads {
graph.add_edge(id.i1, id.i2, id);
if r.oneway_for_driving().is_none() {
graph.add_edge(id.i2, id.i1, id);
}
}
petgraph::algo::dijkstra(&graph, from, Some(to), |(_, _, r)| {
// TODO Expensive!
self.roads[r].length()
})
.get(&to)
.cloned()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RawRoad {
/// This is effectively a PolyLine, except there's a case where we need to plumb forward
/// cul-de-sac roads for roundabout handling. No transformation of these points whatsoever has
/// happened.
pub osm_center_points: Vec<Pt2D>,
pub osm_tags: Tags,
pub turn_restrictions: Vec<(RestrictionType, OriginalRoad)>,
/// (via, to). For turn restrictions where 'via' is an entire road. Only BanTurns.
pub complicated_turn_restrictions: Vec<(OriginalRoad, OriginalRoad)>,
pub percent_incline: f64,
/// Is there a tagged crosswalk near each end of the road?
pub crosswalk_forward: bool,
pub crosswalk_backward: bool,
/// Barrier nodes along this road's original center line.
// TODO Preserving these across transformations (especially merging dual carriageways!) could
// be really hard. It might be better to split the road into two pieces to match the more often
// used OSM style.
pub barrier_nodes: Vec<Pt2D>,
/// Derived from osm_tags. Not automatically updated.
pub lane_specs_ltr: Vec<LaneSpec>,
}
impl RawRoad {
pub fn new(osm_center_points: Vec<Pt2D>, osm_tags: Tags, config: &MapConfig) -> Result<Self> {
// Just flush out errors immediately.
// TODO Store the PolyLine, not a Vec<Pt2D>
let _ = PolyLine::new(osm_center_points.clone())?;
let lane_specs_ltr = get_lane_specs_ltr(&osm_tags, config);
Ok(Self {
osm_center_points,
osm_tags,
turn_restrictions: Vec::new(),
complicated_turn_restrictions: Vec::new(),
percent_incline: 0.0,
// Start assuming there's a crosswalk everywhere, and maybe filter it down
// later
crosswalk_forward: true,
crosswalk_backward: true,
barrier_nodes: Vec::new(),
lane_specs_ltr,
})
}
// TODO For the moment, treating all rail things as light rail
pub fn is_light_rail(&self) -> bool {
self.osm_tags.is_any("railway", vec!["light_rail", "rail"])
}
pub fn is_footway(&self) -> bool {
self.osm_tags.is_any(
osm::HIGHWAY,
vec![
"cycleway",
"footway",
"path",
"pedestrian",
"steps",
"track",
],
)
}
pub fn is_service(&self) -> bool {
self.osm_tags.is(osm::HIGHWAY, "service")
}
pub fn is_cycleway(&self) -> bool {
// Don't repeat the logic looking at the tags, just see what lanes we'll create
let mut bike = false;
for spec in &self.lane_specs_ltr {
if spec.lt == LaneType::Biking {
bike = true;
} else if spec.lt != LaneType::Shoulder {
return false;
}
}
bike
}
pub fn is_driveable(&self) -> bool {
self.lane_specs_ltr
.iter()
.any(|spec| spec.lt == LaneType::Driving)
}
pub fn oneway_for_driving(&self) -> Option<Direction> {
LaneSpec::oneway_for_driving(&self.lane_specs_ltr)
}
/// Points from first to last point. Undefined for loops.
pub fn angle(&self) -> Angle {
self.osm_center_points[0].angle_to(*self.osm_center_points.last().unwrap())
}
pub fn length(&self) -> Distance {
PolyLine::unchecked_new(self.osm_center_points.clone()).length()
}
pub fn get_zorder(&self) -> isize {
if let Some(layer) = self.osm_tags.get("layer") {
match layer.parse::<f64>() {
// Just drop .5 for now
Ok(l) => l as isize,
Err(_) => {
warn!("Weird layer={} on {}", layer, self.osm_url());
0
}
}
} else {
0
}
}
/// Returns the corrected (but untrimmed) center and total width for a road
pub fn untrimmed_road_geometry(&self) -> (PolyLine, Distance) {
let mut total_width = Distance::ZERO;
let mut sidewalk_right = None;
let mut sidewalk_left = None;
for l in &self.lane_specs_ltr {
total_width += l.width;
if l.lt.is_walkable() {
if l.dir == Direction::Back {
sidewalk_left = Some(l.width);
} else {
sidewalk_right = Some(l.width);
}
}
}
// If there's a sidewalk on only one side, adjust the true center of the road.
// TODO I don't remember the rationale for doing this in the first place. What if there's a
// shoulder and a sidewalk of different widths? We don't do anything then
let mut true_center = match PolyLine::new(self.osm_center_points.clone()) {
Ok(pl) => pl,
Err(err) => panic!(
"untrimmed_road_geometry of {} failed: {}",
self.osm_url(),
err
),
};
match (sidewalk_right, sidewalk_left) {
(Some(w), None) => {
true_center = true_center.must_shift_right(w / 2.0);
}
(None, Some(w)) => {
true_center = true_center.must_shift_right(w / 2.0);
}
_ => {}
}
(true_center, total_width)
}
pub fn osm_url(&self) -> String {
// Since we don't store an OriginalRoad (since we may need to update it during
// transformations), this may be convenient
format!(
"http://openstreetmap.org/way/{}",
self.osm_tags.get(osm::OSM_WAY_ID).unwrap()
)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RawIntersection {
/// Represents the original place where OSM center-lines meet. This may be meaningless beyond
/// RawMap; roads and intersections get merged and deleted.
pub point: Pt2D,
pub intersection_type: IntersectionType,
pub elevation: Distance,
// true if src_i matches this intersection (or the deleted/consolidated one, whatever)
pub trim_roads_for_merging: BTreeMap<(osm::WayID, bool), Pt2D>,
}
impl RawIntersection {
pub fn new(point: Pt2D, intersection_type: IntersectionType) -> Self {
Self {
point,
intersection_type,
// Filled out later
elevation: Distance::ZERO,
trim_roads_for_merging: BTreeMap::new(),
}
}
fn is_border(&self) -> bool {
self.intersection_type == IntersectionType::Border
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawBuilding {
pub polygon: Polygon,
@ -559,33 +109,6 @@ pub struct RawParkingLot {
pub osm_tags: Tags,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RestrictionType {
BanTurns,
OnlyAllowTurns,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TurnRestriction(pub OriginalRoad, pub RestrictionType, pub OriginalRoad);
impl RestrictionType {
pub fn new(restriction: &str) -> Option<RestrictionType> {
// TODO There's a huge space of things not represented yet: time conditions, bus-only, no
// right turn on red...
// There are so many possibilities:
// https://taginfo.openstreetmap.org/keys/restriction#values
// Just attempt to bucket into allow / deny.
if restriction.contains("no_") || restriction == "psv" {
Some(RestrictionType::BanTurns)
} else if restriction.contains("only_") {
Some(RestrictionType::OnlyAllowTurns)
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawTransitRoute {
pub long_name: String,

View File

@ -1,19 +1,9 @@
use std::collections::BTreeMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter, EnumString};
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags};
use geom::Distance;
use crate::osm;
pub const NORMAL_LANE_THICKNESS: Distance = Distance::const_meters(2.5);
const SERVICE_ROAD_LANE_THICKNESS: Distance = Distance::const_meters(1.5);
pub const SIDEWALK_THICKNESS: Distance = Distance::const_meters(1.5);
const SHOULDER_THICKNESS: Distance = Distance::const_meters(0.5);
use abstutil::Tags;
use street_network::NamePerLanguage;
/// A business located inside a building.
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -190,49 +180,6 @@ impl AmenityType {
}
}
/// None corresponds to the native name
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct NamePerLanguage(
#[serde(
serialize_with = "serialize_btreemap",
deserialize_with = "deserialize_btreemap"
)]
pub(crate) BTreeMap<Option<String>, String>,
);
impl NamePerLanguage {
pub fn get(&self, lang: Option<&String>) -> &String {
// TODO Can we avoid this clone?
let lang = lang.cloned();
if let Some(name) = self.0.get(&lang) {
return name;
}
&self.0[&None]
}
pub fn new(tags: &Tags) -> Option<NamePerLanguage> {
let native_name = tags.get(osm::NAME)?;
let mut map = BTreeMap::new();
map.insert(None, native_name.to_string());
for (k, v) in tags.inner() {
if let Some(lang) = k.strip_prefix("name:") {
map.insert(Some(lang.to_string()), v.to_string());
}
}
Some(NamePerLanguage(map))
}
pub fn unnamed() -> NamePerLanguage {
let mut map = BTreeMap::new();
map.insert(None, "unnamed".to_string());
NamePerLanguage(map)
}
pub fn languages(&self) -> Vec<&String> {
self.0.keys().flatten().collect()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum AreaType {
Park,
@ -244,343 +191,3 @@ pub enum AreaType {
/// Not from OSM. A user-specified area to focus on.
StudyArea,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Direction {
Fwd,
Back,
}
impl Direction {
pub fn opposite(self) -> Direction {
match self {
Direction::Fwd => Direction::Back,
Direction::Back => Direction::Fwd,
}
}
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Direction::Fwd => write!(f, "forwards"),
Direction::Back => write!(f, "backwards"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MapConfig {
/// If true, driving happens on the right side of the road (USA). If false, on the left
/// (Australia).
pub driving_side: DrivingSide,
pub bikes_can_use_bus_lanes: bool,
/// If true, roads without explicitly tagged sidewalks may have sidewalks or shoulders. If
/// false, no sidewalks will be inferred if not tagged in OSM, and separate sidewalks will be
/// included.
pub inferred_sidewalks: bool,
/// Street parking is divided into spots of this length. 8 meters is a reasonable default, but
/// people in some regions might be more accustomed to squeezing into smaller spaces. This
/// value can be smaller than the hardcoded maximum car length; cars may render on top of each
/// other, but otherwise the simulation doesn't care.
pub street_parking_spot_length: Distance,
/// If true, turns on red which do not conflict crossing traffic ('right on red') are allowed
pub turn_on_red: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum DrivingSide {
Right,
Left,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum IntersectionType {
StopSign,
TrafficSignal,
Border,
Construction,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LaneType {
Driving,
Parking,
Sidewalk,
// Walkable like a Sidewalk, but very narrow. Used to model pedestrians walking on roads
// without sidewalks.
Shoulder,
Biking,
Bus,
SharedLeftTurn,
Construction,
LightRail,
Buffer(BufferType),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BufferType {
/// Just paint!
Stripes,
/// Flex posts, wands, cones, other "weak" forms of protection. Can weave through them.
FlexPosts,
/// Sturdier planters, with gaps.
Planters,
/// Solid barrier, no gaps.
JerseyBarrier,
/// A raised curb
Curb,
}
impl LaneType {
pub fn is_for_moving_vehicles(self) -> bool {
match self {
LaneType::Driving => true,
LaneType::Biking => true,
LaneType::Bus => true,
LaneType::Parking => false,
LaneType::Sidewalk => false,
LaneType::Shoulder => false,
LaneType::SharedLeftTurn => false,
LaneType::Construction => false,
LaneType::LightRail => true,
LaneType::Buffer(_) => false,
}
}
pub fn supports_any_movement(self) -> bool {
match self {
LaneType::Driving => true,
LaneType::Biking => true,
LaneType::Bus => true,
LaneType::Parking => false,
LaneType::Sidewalk => true,
LaneType::Shoulder => true,
LaneType::SharedLeftTurn => false,
LaneType::Construction => false,
LaneType::LightRail => true,
LaneType::Buffer(_) => false,
}
}
pub fn is_walkable(self) -> bool {
self == LaneType::Sidewalk || self == LaneType::Shoulder
}
pub fn describe(self) -> &'static str {
match self {
LaneType::Driving => "a general-purpose driving lane",
LaneType::Biking => "a protected bike lane",
LaneType::Bus => "a bus-only lane",
LaneType::Parking => "an on-street parking lane",
LaneType::Sidewalk => "a sidewalk",
LaneType::Shoulder => "a shoulder",
LaneType::SharedLeftTurn => "a shared left-turn lane",
LaneType::Construction => "a lane that's closed for construction",
LaneType::LightRail => "a light rail track",
LaneType::Buffer(BufferType::Stripes) => "striped pavement",
LaneType::Buffer(BufferType::FlexPosts) => "flex post barriers",
LaneType::Buffer(BufferType::Planters) => "planter barriers",
LaneType::Buffer(BufferType::JerseyBarrier) => "a Jersey barrier",
LaneType::Buffer(BufferType::Curb) => "a raised curb",
}
}
pub fn short_name(self) -> &'static str {
match self {
LaneType::Driving => "driving lane",
LaneType::Biking => "bike lane",
LaneType::Bus => "bus lane",
LaneType::Parking => "parking lane",
LaneType::Sidewalk => "sidewalk",
LaneType::Shoulder => "shoulder",
LaneType::SharedLeftTurn => "left-turn lane",
LaneType::Construction => "construction",
LaneType::LightRail => "light rail track",
LaneType::Buffer(BufferType::Stripes) => "stripes",
LaneType::Buffer(BufferType::FlexPosts) => "flex posts",
LaneType::Buffer(BufferType::Planters) => "planters",
LaneType::Buffer(BufferType::JerseyBarrier) => "Jersey barrier",
LaneType::Buffer(BufferType::Curb) => "curb",
}
}
pub fn from_short_name(x: &str) -> Option<LaneType> {
match x {
"driving lane" => Some(LaneType::Driving),
"bike lane" => Some(LaneType::Biking),
"bus lane" => Some(LaneType::Bus),
"parking lane" => Some(LaneType::Parking),
"sidewalk" => Some(LaneType::Sidewalk),
"shoulder" => Some(LaneType::Shoulder),
"left-turn lane" => Some(LaneType::SharedLeftTurn),
"construction" => Some(LaneType::Construction),
"light rail track" => Some(LaneType::LightRail),
"stripes" => Some(LaneType::Buffer(BufferType::Stripes)),
"flex posts" => Some(LaneType::Buffer(BufferType::FlexPosts)),
"planters" => Some(LaneType::Buffer(BufferType::Planters)),
"Jersey barrier" => Some(LaneType::Buffer(BufferType::JerseyBarrier)),
"curb" => Some(LaneType::Buffer(BufferType::Curb)),
_ => None,
}
}
/// Represents the lane type as a single character, for use in tests.
pub fn to_char(self) -> char {
match self {
LaneType::Driving => 'd',
LaneType::Biking => 'b',
LaneType::Bus => 'B',
LaneType::Parking => 'p',
LaneType::Sidewalk => 's',
LaneType::Shoulder => 'S',
LaneType::SharedLeftTurn => 'C',
LaneType::Construction => 'x',
LaneType::LightRail => 'l',
LaneType::Buffer(_) => '|',
}
}
/// The inverse of `to_char`. Always picks one buffer type. Panics on invalid input.
pub fn from_char(x: char) -> LaneType {
match x {
'd' => LaneType::Driving,
'b' => LaneType::Biking,
'B' => LaneType::Bus,
'p' => LaneType::Parking,
's' => LaneType::Sidewalk,
'S' => LaneType::Shoulder,
'C' => LaneType::SharedLeftTurn,
'x' => LaneType::Construction,
'l' => LaneType::LightRail,
'|' => LaneType::Buffer(BufferType::FlexPosts),
_ => panic!("from_char({}) undefined", x),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LaneSpec {
pub lt: LaneType,
pub dir: Direction,
pub width: Distance,
}
impl LaneSpec {
/// For a given lane type, returns some likely widths. This may depend on the type of the road,
/// so the OSM tags are also passed in. The first value returned will be used as a default.
pub fn typical_lane_widths(lt: LaneType, tags: &Tags) -> Vec<(Distance, &'static str)> {
// These're cobbled together from various sources
match lt {
// https://en.wikipedia.org/wiki/Lane#Lane_width
LaneType::Driving => {
let mut choices = vec![
(Distance::feet(8.0), "narrow"),
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
(Distance::feet(10.0), "typical"),
(Distance::feet(12.0), "highway"),
];
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
choices.swap(1, 0);
}
choices
}
// https://www.gov.uk/government/publications/cycle-infrastructure-design-ltn-120 table
// 5-2
LaneType::Biking => vec![
(Distance::meters(2.0), "standard"),
(Distance::meters(1.5), "absolute minimum"),
],
// https://nacto.org/publication/urban-street-design-guide/street-design-elements/transit-streets/dedicated-curbside-offset-bus-lanes/
LaneType::Bus => vec![
(Distance::feet(12.0), "normal"),
(Distance::feet(10.0), "minimum"),
],
// https://nacto.org/publication/urban-street-design-guide/street-design-elements/lane-width/
LaneType::Parking => {
let mut choices = vec![
(Distance::feet(7.0), "narrow"),
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
(Distance::feet(9.0), "wide"),
(Distance::feet(15.0), "loading zone"),
];
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
choices.swap(1, 0);
}
choices
}
// Just a guess
LaneType::SharedLeftTurn => vec![(NORMAL_LANE_THICKNESS, "default")],
// These're often converted from existing lanes, so just retain that width
LaneType::Construction => vec![(NORMAL_LANE_THICKNESS, "default")],
// No idea, just using this for now...
LaneType::LightRail => vec![(NORMAL_LANE_THICKNESS, "default")],
// http://www.seattle.gov/rowmanual/manual/4_11.asp
LaneType::Sidewalk => vec![
(SIDEWALK_THICKNESS, "default"),
(Distance::feet(6.0), "wide"),
],
LaneType::Shoulder => vec![(SHOULDER_THICKNESS, "default")],
// Pretty wild guesses
LaneType::Buffer(BufferType::Stripes) => vec![(Distance::meters(1.5), "default")],
LaneType::Buffer(BufferType::FlexPosts) => {
vec![(Distance::meters(1.5), "default")]
}
LaneType::Buffer(BufferType::Planters) => {
vec![(Distance::meters(2.0), "default")]
}
LaneType::Buffer(BufferType::JerseyBarrier) => {
vec![(Distance::meters(1.5), "default")]
}
LaneType::Buffer(BufferType::Curb) => vec![(Distance::meters(0.5), "default")],
}
}
/// Put a list of forward and backward lanes into left-to-right order, depending on the driving
/// side. Both input lists should be ordered from the center of the road going outwards.
pub(crate) fn assemble_ltr(
mut fwd_side: Vec<LaneSpec>,
mut back_side: Vec<LaneSpec>,
driving_side: DrivingSide,
) -> Vec<LaneSpec> {
match driving_side {
DrivingSide::Right => {
back_side.reverse();
back_side.extend(fwd_side);
back_side
}
DrivingSide::Left => {
fwd_side.reverse();
fwd_side.extend(back_side);
fwd_side
}
}
}
/// None if bidirectional. If it's one-way, which direction is that relative to the road?
/// (Usually forwards)
pub fn oneway_for_driving(lanes: &[LaneSpec]) -> Option<Direction> {
let mut fwd = false;
let mut back = false;
for x in lanes {
if x.lt == LaneType::Driving {
if x.dir == Direction::Fwd {
fwd = true;
} else {
back = true;
}
}
}
if fwd && back {
// Bidirectional
None
} else if fwd {
Some(Direction::Fwd)
} else if back {
Some(Direction::Back)
} else {
// Not driveable at all
None
}
}
}

18
street_network/Cargo.toml Normal file
View 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"

View File

@ -3,9 +3,9 @@ use geojson::Feature;
use abstutil::Timer;
use crate::RawMap;
use crate::StreetNetwork;
impl RawMap {
impl StreetNetwork {
/// Assumes `run_all_simplifications` has been called if desired
pub fn save_to_geojson(&self, output_path: String, timer: &mut Timer) -> Result<()> {
// TODO InitialMap is going away very soon, but we still need it

View File

@ -1,4 +1,4 @@
//! Naming is confusing, but RawMap -> InitialMap -> Map. InitialMap is separate pretty much just
//! Naming is confusing, but StreetNetwork -> InitialMap -> Map. InitialMap is separate pretty much just
//! for the step of producing <https://a-b-street.github.io/docs/tech/map/importing/geometry.html>.
use std::collections::{BTreeMap, BTreeSet};
@ -6,7 +6,7 @@ use std::collections::{BTreeMap, BTreeSet};
use abstutil::{Tags, Timer};
use geom::{Bounds, Circle, Distance, PolyLine, Polygon, Pt2D};
use crate::{osm, InputRoad, IntersectionType, OriginalRoad, RawMap};
use crate::{osm, InputRoad, IntersectionType, OriginalRoad, StreetNetwork};
pub struct InitialMap {
pub roads: BTreeMap<OriginalRoad, Road>,
@ -28,7 +28,7 @@ pub struct Road {
}
impl Road {
pub fn new(map: &RawMap, id: OriginalRoad) -> Road {
pub fn new(map: &StreetNetwork, id: OriginalRoad) -> Road {
let road = &map.roads[&id];
let (trimmed_center_pts, total_width) = road.untrimmed_road_geometry();
@ -62,7 +62,7 @@ pub struct Intersection {
}
impl InitialMap {
pub fn new(raw: &RawMap, bounds: &Bounds, timer: &mut Timer) -> InitialMap {
pub fn new(raw: &StreetNetwork, bounds: &Bounds, timer: &mut Timer) -> InitialMap {
let mut m = InitialMap {
roads: BTreeMap::new(),
intersections: BTreeMap::new(),

508
street_network/src/lib.rs Normal file
View 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
}
}
}

View File

@ -5,12 +5,12 @@ use anyhow::Result;
use geom::{Distance, Pt2D};
use crate::osm::NodeID;
use crate::{osm, IntersectionType, OriginalRoad, RawMap};
use crate::{osm, IntersectionType, OriginalRoad, StreetNetwork};
/// Collapse degenerate intersections:
/// - between two cycleways
/// - when the lane specs match and only "unimportant" OSM tags differ
pub fn collapse(raw: &mut RawMap) {
pub fn collapse(raw: &mut StreetNetwork) {
let mut merge: Vec<NodeID> = Vec::new();
for id in raw.intersections.keys() {
let roads = raw.roads_per_intersection(*id);
@ -35,7 +35,7 @@ pub fn collapse(raw: &mut RawMap) {
// Results look good so far.
}
fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &RawMap) -> Result<()> {
fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &StreetNetwork) -> Result<()> {
let road1 = &raw.roads[&r1];
let road2 = &raw.roads[&r2];
@ -135,7 +135,7 @@ fn should_collapse(r1: OriginalRoad, r2: OriginalRoad, raw: &RawMap) -> Result<(
Ok(())
}
pub fn collapse_intersection(raw: &mut RawMap, i: NodeID) {
pub fn collapse_intersection(raw: &mut StreetNetwork, i: NodeID) {
let roads = raw.roads_per_intersection(i);
assert_eq!(roads.len(), 2);
let mut r1 = roads[0];
@ -226,7 +226,7 @@ const SHORT_THRESHOLD: Distance = Distance::const_meters(30.0);
/// "stubs." Trim those.
///
/// Also do the same thing for extremely short dead-end service roads.
pub fn trim_deadends(raw: &mut RawMap) {
pub fn trim_deadends(raw: &mut StreetNetwork) {
let mut remove_roads = BTreeSet::new();
let mut remove_intersections = BTreeSet::new();
for (id, i) in &raw.intersections {

View File

@ -1,12 +1,12 @@
use std::collections::BTreeSet;
use crate::{osm, OriginalRoad, RawMap};
use crate::{osm, OriginalRoad, StreetNetwork};
/// Does this road go between two divided one-ways? Ideally they're tagged explicitly
/// (https://wiki.openstreetmap.org/wiki/Tag:dual_carriageway%3Dyes), but we can also apply simple
/// heuristics to guess this.
#[allow(unused)]
pub fn connects_dual_carriageway(map: &RawMap, id: &OriginalRoad) -> bool {
pub fn connects_dual_carriageway(map: &StreetNetwork, id: &OriginalRoad) -> bool {
let connectors_angle = map.roads[id].angle();
// There are false positives like https://www.openstreetmap.org/way/4636259 when we're looking
// at a segment along a marked dual carriageway. Filter out by requiring the intersecting dual

View File

@ -1,8 +1,7 @@
use abstio::MapName;
use abstutil::Timer;
use geom::Distance;
use crate::{osm, IntersectionType, OriginalRoad, RawMap};
use crate::{osm, IntersectionType, OriginalRoad, StreetNetwork};
/// Combines a few different sources/methods to decide which roads are short. Marks them for
/// merging.
@ -10,7 +9,7 @@ use crate::{osm, IntersectionType, OriginalRoad, RawMap};
/// 1) Anything tagged in OSM
/// 2) Anything a temporary local merge_osm_ways.json file
/// 3) If `consolidate_all` is true, an experimental distance-based heuristic
pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<OriginalRoad> {
pub fn find_short_roads(map: &mut StreetNetwork, consolidate_all: bool) -> Vec<OriginalRoad> {
let mut roads = Vec::new();
for (id, road) in &map.roads {
if road.osm_tags.is("junction", "intersection") {
@ -24,21 +23,7 @@ pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<Original
}
// Gradually rolling out
if vec![
MapName::seattle("montlake"),
MapName::seattle("downtown"),
MapName::seattle("lakeslice"),
MapName::new("us", "phoenix", "tempe"),
MapName::new("gb", "bristol", "east"),
//MapName::new("gb", "leeds", "north"),
//MapName::new("gb", "london", "camden"),
MapName::new("gb", "london", "kennington"),
//MapName::new("gb", "london", "southwark"),
//MapName::new("gb", "manchester", "levenshulme"),
MapName::new("pl", "krakow", "center"),
]
.contains(&map.name)
{
if map.config.find_dog_legs_experiment {
roads.extend(map.find_dog_legs());
}
@ -54,7 +39,7 @@ pub fn find_short_roads(map: &mut RawMap, consolidate_all: bool) -> Vec<Original
map.mark_short_roads(roads)
}
fn distance_heuristic(id: OriginalRoad, map: &RawMap) -> bool {
fn distance_heuristic(id: OriginalRoad, map: &StreetNetwork) -> bool {
let road_length = if let Ok(pl) = map.trimmed_road_geometry(id) {
pl.length()
} else {
@ -67,7 +52,7 @@ fn distance_heuristic(id: OriginalRoad, map: &RawMap) -> bool {
road_length < Distance::meters(5.0)
}
impl RawMap {
impl StreetNetwork {
fn mark_short_roads(&mut self, list: Vec<OriginalRoad>) -> Vec<OriginalRoad> {
for id in &list {
self.roads
@ -172,7 +157,7 @@ impl RawMap {
}
// TODO Dedupe with find_divided_highways logic in parking_mapper
fn dual_carriageway_split(map: &RawMap, roads: Vec<OriginalRoad>) -> bool {
fn dual_carriageway_split(map: &StreetNetwork, roads: Vec<OriginalRoad>) -> bool {
assert_eq!(roads.len(), 3);
// Look for one-way roads with the same name
for (r1, r2) in [

View File

@ -2,13 +2,13 @@ use std::collections::{BTreeMap, VecDeque};
use anyhow::Result;
use crate::{osm, IntersectionType, OriginalRoad, RawMap, RestrictionType};
use crate::{osm, IntersectionType, OriginalRoad, RestrictionType, StreetNetwork};
// TODO After merging a road, trying to drag the surviving intersection in map_editor crashes. I
// bet the underlying problem there would help debug automated transformations near merged roads
// too.
impl RawMap {
impl StreetNetwork {
/// Returns (the surviving intersection, the deleted intersection, deleted roads, new roads)
pub fn merge_short_road(
&mut self,
@ -192,7 +192,7 @@ impl RawMap {
}
/// Merge all roads marked with `junction=intersection`
pub fn merge_all_junctions(map: &mut RawMap) {
pub fn merge_all_junctions(map: &mut StreetNetwork) {
let mut queue: VecDeque<OriginalRoad> = VecDeque::new();
for (id, road) in &map.roads {
if road.osm_tags.is("junction", "intersection") {

View File

@ -1,6 +1,6 @@
use abstutil::Timer;
use crate::RawMap;
use crate::StreetNetwork;
mod collapse_intersections;
mod dual_carriageways;
@ -10,20 +10,20 @@ mod remove_disconnected;
mod shrink_roads;
mod snappy;
impl RawMap {
impl StreetNetwork {
// TODO I suspect we'll soon take a full struct of options, maybe even a list of transformation
// enums to run in order
/// Run a sequence of transformations to the RawMap before converting it to a full Map.
/// Run a sequence of transformations to the StreetNetwork before converting it to a full Map.
///
/// We don't want to run these during the OSM->RawMap import stage, because we want to use the
/// map_editor tool to debug the RawMap.
/// We don't want to run these during the OSM->StreetNetwork import stage, because we want to use the
/// map_editor tool to debug the StreetNetwork.
pub fn run_all_simplifications(
&mut self,
consolidate_all_intersections: bool,
remove_disconnected: bool,
timer: &mut Timer,
) {
timer.start("simplify RawMap");
timer.start("simplify StreetNetwork");
timer.start("trimming dead-end cycleways (round 1)");
collapse_intersections::trim_deadends(self);
@ -56,6 +56,6 @@ impl RawMap {
shrink_roads::shrink(self, timer);
timer.stop("shrinking overlapping roads");
timer.stop("simplify RawMap");
timer.stop("simplify StreetNetwork");
}
}

View File

@ -2,11 +2,11 @@ use std::collections::BTreeSet;
use abstutil::{MultiMap, Timer};
use crate::{osm, OriginalRoad, RawMap};
use crate::{osm, OriginalRoad, StreetNetwork};
/// Some roads might be totally disconnected from the largest clump because of how the map's
/// bounding polygon was drawn, or bad map data, or which roads are filtered from OSM. Remove them.
pub fn remove_disconnected_roads(map: &mut RawMap, timer: &mut Timer) {
pub fn remove_disconnected_roads(map: &mut StreetNetwork, timer: &mut Timer) {
timer.start("removing disconnected roads");
// This is a simple floodfill, not Tarjan's. Assumes all roads bidirectional.
// All the usizes are indices into the original list of roads

View File

@ -3,11 +3,11 @@ use std::collections::{HashMap, HashSet};
use aabb_quadtree::QuadTree;
use abstutil::Timer;
use crate::{osm, RawMap};
use crate::{osm, StreetNetwork};
/// Look for roads that physically overlap, but aren't connected by an intersection. Shrink their
/// width.
pub fn shrink(raw: &mut RawMap, timer: &mut Timer) {
pub fn shrink(raw: &mut StreetNetwork, timer: &mut Timer) {
let mut road_centers = HashMap::new();
let mut road_polygons = HashMap::new();
let mut overlapping = Vec::new();

View File

@ -4,12 +4,12 @@ use abstutil::MultiMap;
use geom::{Distance, FindClosest, Line, PolyLine};
use kml::{ExtraShape, ExtraShapes};
use crate::{Direction, OriginalRoad, RawMap};
use crate::{Direction, OriginalRoad, StreetNetwork};
const DEBUG_OUTPUT: bool = true;
/// Snap separately mapped cycleways to main roads.
pub fn snap_cycleways(map: &mut RawMap) {
pub fn snap_cycleways(map: &mut StreetNetwork) {
if true {
return;
}
@ -123,7 +123,7 @@ struct Cycleway {
// cycleways hit.
// TODO Or look for cycleway polygons strictly overlapping thick road polygons
fn v1(
map: &RawMap,
map: &StreetNetwork,
cycleways: &[Cycleway],
road_edges: &HashMap<(OriginalRoad, Direction), PolyLine>,
) -> MultiMap<OriginalRoad, (OriginalRoad, Direction)> {
@ -220,16 +220,19 @@ fn v1(
}
}
if DEBUG_OUTPUT {
// TODO If this is still useful, figure out how to wire it up
let _debug_shapes = ExtraShapes {
shapes: debug_shapes,
};
/*if DEBUG_OUTPUT {
abstio::write_binary(
map.name
.city
.input_path(format!("{}_snapping.bin", map.name.map)),
&ExtraShapes {
shapes: debug_shapes,
},
&debug_shapes
);
}
}*/
matches
}

399
street_network/src/types.rs Normal file
View 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
}
}
}