-
-
Notifications
You must be signed in to change notification settings - Fork 341
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A first attempt to snap separately mapped cycleways to main roads. Em…
…it (#348) an extra KML file during importing to debug; don't bring into the main map yet. #330 Not regenerating yet
- Loading branch information
1 parent
f0be8dc
commit 5a3bee0
Showing
6 changed files
with
168 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
use abstutil::MultiMap; | ||
use abstutil::Timer; | ||
use geom::{Distance, FindClosest, Line, PolyLine}; | ||
use kml::{ExtraShape, ExtraShapes}; | ||
use map_model::osm::WayID; | ||
use map_model::raw::{OriginalRoad, RawMap}; | ||
use map_model::{osm, Direction}; | ||
use std::collections::{BTreeMap, HashMap, HashSet}; | ||
|
||
// Attempt to snap separately mapped cycleways to main roads. Emit extra KML files to debug later. | ||
pub fn snap_cycleways(map: &RawMap, timer: &mut Timer) { | ||
let mut cycleways = HashMap::new(); | ||
for shape in abstutil::read_binary::<ExtraShapes>( | ||
abstutil::path(format!("input/{}/footways.bin", map.city_name)), | ||
timer, | ||
) | ||
.shapes | ||
{ | ||
// Just cycleways for now. This same general strategy should later work for sidewalks, | ||
// tramways, and blockface parking too. | ||
if shape.attributes.get("highway") == Some(&"cycleway".to_string()) { | ||
cycleways.insert( | ||
WayID(shape.attributes[osm::OSM_WAY_ID].parse().unwrap()), | ||
shape, | ||
); | ||
} | ||
} | ||
|
||
let mut road_edges: HashMap<(OriginalRoad, Direction), PolyLine> = HashMap::new(); | ||
for (id, r) in &map.roads { | ||
if r.is_light_rail() || r.is_footway() { | ||
continue; | ||
} | ||
let (pl, total_width) = r.get_geometry(*id, map.config.driving_side); | ||
road_edges.insert( | ||
(*id, Direction::Fwd), | ||
pl.must_shift_right(total_width / 2.0), | ||
); | ||
road_edges.insert( | ||
(*id, Direction::Back), | ||
pl.must_shift_left(total_width / 2.0), | ||
); | ||
} | ||
|
||
let matches = v1(map, &cycleways, &road_edges); | ||
// TODO A v2 idea: just look for cycleways strictly overlapping a thick road polygon | ||
dump_output(map, &cycleways, &road_edges, matches); | ||
} | ||
|
||
fn dump_output( | ||
map: &RawMap, | ||
cycleways: &HashMap<WayID, ExtraShape>, | ||
road_edges: &HashMap<(OriginalRoad, Direction), PolyLine>, | ||
matches: MultiMap<(OriginalRoad, Direction), WayID>, | ||
) { | ||
let mut separate_cycleways = ExtraShapes { shapes: Vec::new() }; | ||
let mut snapped_cycleways = ExtraShapes { shapes: Vec::new() }; | ||
|
||
let mut used_cycleways = HashSet::new(); | ||
for ((r, dir), ids) in matches.consume() { | ||
let mut attributes = BTreeMap::new(); | ||
used_cycleways.extend(ids.clone()); | ||
attributes.insert( | ||
"cycleways".to_string(), | ||
ids.into_iter() | ||
.map(|x| x.to_string()) | ||
.collect::<Vec<_>>() | ||
.join(", "), | ||
); | ||
snapped_cycleways.shapes.push(ExtraShape { | ||
points: map.gps_bounds.convert_back(road_edges[&(r, dir)].points()), | ||
attributes, | ||
}); | ||
} | ||
|
||
for (id, shape) in cycleways { | ||
if !used_cycleways.contains(id) { | ||
separate_cycleways.shapes.push(shape.clone()); | ||
} | ||
} | ||
|
||
abstutil::write_binary( | ||
abstutil::path(format!( | ||
"input/{}/{}_separate_cycleways.bin", | ||
map.city_name, map.name | ||
)), | ||
&separate_cycleways, | ||
); | ||
abstutil::write_binary( | ||
abstutil::path(format!( | ||
"input/{}/{}_snapped_cycleways.bin", | ||
map.city_name, map.name | ||
)), | ||
&snapped_cycleways, | ||
); | ||
} | ||
|
||
// Walk along every cycleway, form a perpendicular line, and mark all road edges that it hits. | ||
// | ||
// TODO Inverse idea: Walk every road, project perpendicular from each of the 4 corners and see what | ||
// cycleways hit. | ||
// | ||
// TODO Should we run this before splitting ways? Possibly less work to do. | ||
fn v1( | ||
map: &RawMap, | ||
cycleways: &HashMap<WayID, ExtraShape>, | ||
road_edges: &HashMap<(OriginalRoad, Direction), PolyLine>, | ||
) -> MultiMap<(OriginalRoad, Direction), WayID> { | ||
let mut matches: MultiMap<(OriginalRoad, Direction), WayID> = MultiMap::new(); | ||
|
||
let mut closest: FindClosest<(OriginalRoad, Direction)> = | ||
FindClosest::new(&map.gps_bounds.to_bounds()); | ||
for (id, pl) in road_edges { | ||
closest.add(*id, pl.points()); | ||
} | ||
|
||
// TODO If this is too large, we might miss some intermediate pieces of the road. | ||
let step_size = Distance::meters(5.0); | ||
// This gives the length of the perpendicular test line | ||
let cycleway_half_width = Distance::meters(3.0); | ||
// How many degrees difference to consider parallel ways | ||
let parallel_threshold = 30.0; | ||
for (cycleway_id, cycleway) in cycleways { | ||
let pl = PolyLine::must_new(map.gps_bounds.convert(&cycleway.points)); | ||
|
||
let mut dist = Distance::ZERO; | ||
loop { | ||
let (pt, cycleway_angle) = pl.must_dist_along(dist); | ||
let perp_line = Line::must_new( | ||
pt.project_away(cycleway_half_width, cycleway_angle.rotate_degs(90.0)), | ||
pt.project_away(cycleway_half_width, cycleway_angle.rotate_degs(-90.0)), | ||
); | ||
for (id, _, _) in closest.all_close_pts(perp_line.pt1(), cycleway_half_width) { | ||
if let Some((_, road_angle)) = | ||
road_edges[&id].intersection(&perp_line.to_polyline()) | ||
{ | ||
if road_angle.approx_eq(cycleway_angle, parallel_threshold) { | ||
matches.insert(id, *cycleway_id); | ||
// TODO Just stop at the first hit? | ||
break; | ||
} | ||
} | ||
} | ||
|
||
if dist == pl.length() { | ||
break; | ||
} | ||
dist += step_size; | ||
dist = dist.min(pl.length()); | ||
} | ||
} | ||
|
||
matches | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters