Skip to content

Commit

Permalink
A first attempt to snap separately mapped cycleways to main roads. Emit
Browse files Browse the repository at this point in the history
an extra KML file during importing to debug; don't bring into the main
map yet. #330

Not regenerating yet
  • Loading branch information
dabreegster committed Sep 25, 2020
1 parent 0f78920 commit 292bf8a
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 15 deletions.
17 changes: 5 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,16 @@ __pycache__
data/config

data/input/berlin/osm
data/input/berlin/footways.bin
data/input/berlin/service_roads.bin
data/input/berlin/planning_areas.bin
data/input/berlin/planning_areas.kml
data/input/berlin/EWR201812E_Matrix.csv

data/input/krakow/osm
data/input/krakow/footways.bin
data/input/krakow/service_roads.bin

data/input/london/osm
data/input/london/footways.bin
data/input/london/service_roads.bin

data/input/seattle/blockface.bin
data/input/seattle/blockface.kml
data/input/seattle/footways.bin
data/input/seattle/google_transit
data/input/seattle/N47W122.hgt
data/input/seattle/offstreet_parking.bin
Expand All @@ -30,16 +23,16 @@ data/input/seattle/osm
data/input/seattle/parcels.bin
data/input/seattle/parcels_urbansim.txt
data/input/seattle/popdat.bin
data/input/seattle/service_roads.bin
data/input/seattle/trips_2014.csv

data/input/tel_aviv/osm
data/input/tel_aviv/footways.bin
data/input/tel_aviv/service_roads.bin

data/input/xian/osm
data/input/xian/footways.bin
data/input/xian/service_roads.bin

data/input/*/footways.bin
data/input/*/service_roads.bin
data/input/*/*_separate_cycleways.bin
data/input/*/*_snapped_cycleways.bin

data/input/raw_maps
data/input/screenshots/*.zip
Expand Down
3 changes: 3 additions & 0 deletions convert_osm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod clip;
mod extract;
mod parking;
mod reader;
mod snappy;
mod split_ways;
mod srtm;
mod transit;
Expand Down Expand Up @@ -102,6 +103,8 @@ pub fn convert(opts: Options, timer: &mut abstutil::Timer) -> RawMap {
use_elevation(&mut map, path, timer);
}

snappy::snap_cycleways(&mut map, timer);

map.config = opts.map_config;
map
}
Expand Down
154 changes: 154 additions & 0 deletions convert_osm/src/snappy.rs
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
}
5 changes: 4 additions & 1 deletion game/src/devtools/kml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ impl State for ViewKML {
.collect(),
),
Box::new(|path, ctx, app| {
Transition::Replace(ViewKML::new(ctx, app, Some(path)))
Transition::Multi(vec![
Transition::Pop,
Transition::Replace(ViewKML::new(ctx, app, Some(path))),
])
}),
));
}
Expand Down
2 changes: 1 addition & 1 deletion kml/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct ExtraShapes {
pub shapes: Vec<ExtraShape>,
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtraShape {
pub points: Vec<LonLat>,
pub attributes: BTreeMap<String, String>,
Expand Down
2 changes: 1 addition & 1 deletion map_model/src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ pub struct RawRoad {
}

impl RawRoad {
// Returns the corrected center and half width
// Returns the corrected center and total width
pub fn get_geometry(
&self,
id: OriginalRoad,
Expand Down

0 comments on commit 292bf8a

Please sign in to comment.