organizing map making code a little

This commit is contained in:
Dustin Carlino 2018-06-27 13:17:15 -07:00
parent 849a132a6a
commit 69d6c4ab43
10 changed files with 327 additions and 266 deletions

View File

@ -69,3 +69,25 @@ timescale for a traffic simulation. :) So, let's switch to u32.
easy to understand and test. Maybe the sim_ctrl pattern is nicer? A light
adapter to control the thing from the UI? ezgui's textbox and menu are similar
-- no rendering, some input handling.
## Map making
Stages are roughly:
- extract parcels inside a bbox from a .kml
- load elevation into memory from a .hgt
- get raw OSM ways and bbox from a .osm
- (elevation, raw OSM ways) -> split up OSM stuff
- merge in the parcels fitting the specific bbox
- load traffic signal from a .shp and match to nearest intersection
- create finalish Intersection structs
- * split roads into lanes based on lane specs. also update Intersections.
- * trim road lines for each intersection
- * make turns for each intersection
- * make each building, finding the front path using lanes
- map over parcels directly
The live edits will modify lane specs and turns. Will have to re-do starred
items most likely. Should be straightforward to only redo small parts of those
stages.

View File

@ -119,7 +119,8 @@ wait slow down even more -- before any of this change, lanes on adjacent roads s
- some bldg paths are quite long.
- make final Map serializable too
- useful to precompute sidewalk paths
- reorg map making
- waiting on https://github.com/paholg/dimensioned/issues/31 to release
- cant easily serialize ordered float, so move away from it first?
- small geometry refactorings (like shifting polyline on opposite side, reversing pts)

View File

@ -1,11 +1,6 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use LaneType;
use Map;
use Pt2D;
use RoadID;
use geo;
use ordered_float::NotNaN;
use std::collections::HashMap;
// TODO reconsider pub usize. maybe outside world shouldnt know.
@ -27,62 +22,3 @@ impl PartialEq for Building {
self.id == other.id
}
}
pub(crate) fn find_front_path(
bldg_points: &Vec<Pt2D>,
bldg_osm_tags: &HashMap<String, String>,
map: &Map,
) -> Option<(Pt2D, Pt2D)> {
use geo::prelude::{ClosestPoint, EuclideanDistance};
if let Some(street_name) = bldg_osm_tags.get("addr:street") {
// TODO start from the side of the building, not the center
let bldg_center = center(bldg_points);
let center_pt = geo::Point::new(bldg_center.x(), bldg_center.y());
// Find all matching sidewalks with that street name, then find the closest point on
// that sidewalk
let candidates: Vec<(RoadID, geo::Point<f64>)> = map.all_roads()
.iter()
.filter_map(|r| {
if r.lane_type == LaneType::Sidewalk && r.osm_tags.get("name") == Some(street_name)
{
if let geo::Closest::SinglePoint(pt) =
road_to_line_string(r.id, map).closest_point(&center_pt)
{
return Some((r.id, pt));
}
}
None
})
.collect();
if let Some(closest) = candidates
.iter()
.min_by_key(|pair| NotNaN::new(pair.1.euclidean_distance(&center_pt)).unwrap())
{
return Some((bldg_center, Pt2D::new(closest.1.x(), closest.1.y())));
}
}
None
}
fn center(pts: &Vec<Pt2D>) -> Pt2D {
let mut x = 0.0;
let mut y = 0.0;
for pt in pts {
x += pt.x();
y += pt.y();
}
let len = pts.len() as f64;
Pt2D::new(x / len, y / len)
}
fn road_to_line_string(r: RoadID, map: &Map) -> geo::LineString<f64> {
let pts: Vec<geo::Point<f64>> = map.get_r(r)
.lane_center_pts
.iter()
.map(|pt| geo::Point::new(pt.x(), pt.y()))
.collect();
pts.into()
}

View File

@ -16,6 +16,7 @@ extern crate vecmath;
mod building;
pub mod geometry;
mod intersection;
mod make;
mod map;
mod parcel;
mod polyline;

View File

@ -0,0 +1,92 @@
use Bounds;
use Building;
use BuildingID;
use LaneType;
use Pt2D;
use Road;
use RoadID;
use geo;
use geometry;
use ordered_float::NotNaN;
use raw_data;
use std::collections::HashMap;
pub(crate) fn make_building(
b: &raw_data::Building,
id: BuildingID,
bounds: &Bounds,
roads: &Vec<Road>,
) -> Building {
// TODO consume data, so we dont have to clone tags?
let points = b.points
.iter()
.map(|coord| geometry::gps_to_screen_space(&Pt2D::from(coord), bounds))
.collect();
let front_path = find_front_path(&points, &b.osm_tags, roads);
Building {
points,
front_path,
id,
osm_way_id: b.osm_way_id,
osm_tags: b.osm_tags.clone(),
}
}
fn find_front_path(
bldg_points: &Vec<Pt2D>,
bldg_osm_tags: &HashMap<String, String>,
roads: &Vec<Road>,
) -> Option<(Pt2D, Pt2D)> {
use geo::prelude::{ClosestPoint, EuclideanDistance};
if let Some(street_name) = bldg_osm_tags.get("addr:street") {
// TODO start from the side of the building, not the center
let bldg_center = center(bldg_points);
let center_pt = geo::Point::new(bldg_center.x(), bldg_center.y());
// Find all matching sidewalks with that street name, then find the closest point on
// that sidewalk
let candidates: Vec<(RoadID, geo::Point<f64>)> = roads
.iter()
.filter_map(|r| {
if r.lane_type == LaneType::Sidewalk && r.osm_tags.get("name") == Some(street_name)
{
if let geo::Closest::SinglePoint(pt) =
road_to_line_string(&roads[r.id.0]).closest_point(&center_pt)
{
return Some((r.id, pt));
}
}
None
})
.collect();
if let Some(closest) = candidates
.iter()
.min_by_key(|pair| NotNaN::new(pair.1.euclidean_distance(&center_pt)).unwrap())
{
return Some((bldg_center, Pt2D::new(closest.1.x(), closest.1.y())));
}
}
None
}
fn center(pts: &Vec<Pt2D>) -> Pt2D {
let mut x = 0.0;
let mut y = 0.0;
for pt in pts {
x += pt.x();
y += pt.y();
}
let len = pts.len() as f64;
Pt2D::new(x / len, y / len)
}
fn road_to_line_string(r: &Road) -> geo::LineString<f64> {
let pts: Vec<geo::Point<f64>> = r.lane_center_pts
.iter()
.map(|pt| geo::Point::new(pt.x(), pt.y()))
.collect();
pts.into()
}

View File

@ -0,0 +1,87 @@
use raw_data;
use road::LaneType;
use std::iter;
// (original direction, reversed direction)
fn get_lanes(r: &raw_data::Road) -> (Vec<LaneType>, Vec<LaneType>) {
let oneway = r.osm_tags.get("oneway") == Some(&"yes".to_string());
// These seem to represent weird roundabouts
let junction = r.osm_tags.get("junction") == Some(&"yes".to_string());
let big_road = r.osm_tags.get("highway") == Some(&"primary".to_string())
|| r.osm_tags.get("highway") == Some(&"secondary".to_string());
// TODO debugging convenience
let only_roads_for_debugging = false;
if junction {
return (vec![LaneType::Driving], Vec::new());
}
let num_driving_lanes = if big_road { 2 } else { 1 };
let driving_lanes: Vec<LaneType> = iter::repeat(LaneType::Driving)
.take(num_driving_lanes)
.collect();
if only_roads_for_debugging {
if oneway {
return (driving_lanes, Vec::new());
} else {
return (driving_lanes.clone(), driving_lanes);
}
}
let mut full_side = driving_lanes;
full_side.push(LaneType::Parking);
full_side.push(LaneType::Sidewalk);
if oneway {
(full_side, vec![LaneType::Sidewalk])
} else {
(full_side.clone(), full_side)
}
}
pub(crate) struct LaneSpec {
pub lane_type: LaneType,
pub offset: u8,
pub reverse_pts: bool,
pub offset_for_other_id: Option<isize>,
}
pub(crate) fn get_lane_specs(r: &raw_data::Road) -> Vec<LaneSpec> {
let mut specs: Vec<LaneSpec> = Vec::new();
let (side1_types, side2_types) = get_lanes(r);
for (idx, lane_type) in side1_types.iter().enumerate() {
// TODO this might be a bit wrong. add unit tests. :)
let offset_for_other_id = if *lane_type != LaneType::Driving {
None
} else if !side2_types.contains(&LaneType::Driving) {
None
} else if side1_types == side2_types {
Some(side1_types.len() as isize)
} else {
panic!("get_lane_specs case not handled yet");
};
specs.push(LaneSpec {
offset_for_other_id,
lane_type: *lane_type,
offset: idx as u8,
reverse_pts: false,
});
}
for (idx, lane_type) in side2_types.iter().enumerate() {
let offset_for_other_id = if *lane_type != LaneType::Driving {
None
} else {
Some(-1 * (side1_types.len() as isize))
};
specs.push(LaneSpec {
offset_for_other_id,
lane_type: *lane_type,
offset: idx as u8,
reverse_pts: true,
});
}
specs
}

View File

@ -0,0 +1,9 @@
mod buildings;
mod lanes;
mod trim_lines;
mod turns;
pub(crate) use self::buildings::make_building;
pub(crate) use self::lanes::get_lane_specs;
pub(crate) use self::trim_lines::trim_lines;
pub(crate) use self::turns::make_turns;

View File

@ -0,0 +1,54 @@
use Pt2D;
use dimensioned::si;
use geometry;
use intersection::Intersection;
use road::{Road, RoadID};
use std::collections::HashMap;
use std::collections::hash_map::Entry;
pub(crate) fn trim_lines(roads: &mut Vec<Road>, i: &Intersection) {
let mut shortest_first_line: HashMap<RoadID, (Pt2D, Pt2D, si::Meter<f64>)> = HashMap::new();
let mut shortest_last_line: HashMap<RoadID, (Pt2D, Pt2D, si::Meter<f64>)> = HashMap::new();
fn update_shortest(
m: &mut HashMap<RoadID, (Pt2D, Pt2D, si::Meter<f64>)>,
r: RoadID,
l: (Pt2D, Pt2D),
) {
let new_len = geometry::euclid_dist(l);
match m.entry(r) {
Entry::Occupied(mut o) => {
if new_len < o.get().2 {
o.insert((l.0, l.1, new_len));
}
}
Entry::Vacant(v) => {
v.insert((l.0, l.1, new_len));
}
}
}
// For short first/last lines, this might not work well
for incoming in &i.incoming_roads {
for outgoing in &i.outgoing_roads {
let l1 = roads[incoming.0].last_line();
let l2 = roads[outgoing.0].first_line();
if let Some(hit) = geometry::line_segment_intersection(l1, l2) {
update_shortest(&mut shortest_last_line, *incoming, (l1.0, hit));
update_shortest(&mut shortest_first_line, *outgoing, (hit, l2.1));
}
}
}
// Apply the updates
for (id, triple) in &shortest_first_line {
roads[id.0].lane_center_pts[0] = triple.0;
roads[id.0].lane_center_pts[1] = triple.1;
}
for (id, triple) in &shortest_last_line {
let len = roads[id.0].lane_center_pts.len();
roads[id.0].lane_center_pts[len - 2] = triple.0;
roads[id.0].lane_center_pts[len - 1] = triple.1;
}
}

View File

@ -0,0 +1,52 @@
use Map;
use intersection::Intersection;
use road::{LaneType, RoadID};
use turn::{Turn, TurnID};
pub(crate) fn make_turns(i: &Intersection, m: &Map, turn_id_start: usize) -> Vec<Turn> {
let incoming: Vec<RoadID> = i.incoming_roads
.iter()
// TODO why's this double borrow happen?
.filter(|id| m.get_r(**id).lane_type == LaneType::Driving)
.map(|id| *id)
.collect();
let outgoing: Vec<RoadID> = i.outgoing_roads
.iter()
.filter(|id| m.get_r(**id).lane_type == LaneType::Driving)
.map(|id| *id)
.collect();
// TODO: Figure out why this happens in the huge map
if incoming.is_empty() {
println!("WARNING: intersection {:?} has no incoming roads", i);
return Vec::new();
}
if outgoing.is_empty() {
println!("WARNING: intersection {:?} has no outgoing roads", i);
return Vec::new();
}
let dead_end = incoming.len() == 1 && outgoing.len() == 1;
let mut result = Vec::new();
for src in &incoming {
let src_r = m.get_r(*src);
for dst in &outgoing {
let dst_r = m.get_r(*dst);
// Don't create U-turns unless it's a dead-end
if src_r.other_side == Some(dst_r.id) && !dead_end {
continue;
}
let id = TurnID(turn_id_start + result.len());
result.push(Turn {
id,
parent: i.id,
src: *src,
dst: *dst,
src_pt: src_r.last_pt(),
dst_pt: dst_r.first_pt(),
});
}
}
result
}

View File

@ -3,18 +3,17 @@
use Bounds;
use Pt2D;
use abstutil;
use building;
use building::{Building, BuildingID};
use dimensioned::si;
use geometry;
use intersection::{Intersection, IntersectionID};
use make;
use parcel::{Parcel, ParcelID};
use raw_data;
use road::{LaneType, Road, RoadID};
use road::{Road, RoadID};
use shift_polyline;
use std::collections::HashMap;
use std::io::Error;
use std::iter;
use turn::{Turn, TurnID};
pub struct Map {
@ -63,7 +62,8 @@ impl Map {
let mut counter = 0;
for r in &data.roads {
for lane in get_lane_specs(r) {
// TODO move this to make/lanes.rs too
for lane in make::get_lane_specs(r) {
let id = RoadID(counter);
counter += 1;
let other_side = lane.offset_for_other_id
@ -114,32 +114,20 @@ impl Map {
}
for i in &m.intersections {
trim_lines(&mut m.roads, i);
make::trim_lines(&mut m.roads, i);
}
for i in &m.intersections {
let turns = make_turns(i, &m);
let turns = make::make_turns(i, &m, m.turns.len());
m.turns.extend(turns);
}
for t in &m.turns {
m.intersections[t.parent.0].turns.push(t.id);
}
// TODO consume data, so we dont have to clone tags?
for (idx, b) in data.buildings.iter().enumerate() {
let points = b.points
.iter()
.map(|coord| geometry::gps_to_screen_space(&Pt2D::from(coord), &bounds))
.collect();
let front_path = building::find_front_path(&points, &b.osm_tags, &m);
m.buildings.push(Building {
points,
front_path,
id: BuildingID(idx),
osm_way_id: b.osm_way_id,
osm_tags: b.osm_tags.clone(),
});
m.buildings
.push(make::make_building(b, BuildingID(idx), &bounds, &m.roads));
}
for (idx, p) in data.parcels.iter().enumerate() {
@ -236,184 +224,3 @@ impl Map {
self.bounds.clone()
}
}
// TODO organize these differently
fn make_turns(i: &Intersection, m: &Map) -> Vec<Turn> {
let incoming: Vec<RoadID> = i.incoming_roads
.iter()
.filter(|id| m.roads[id.0].lane_type == LaneType::Driving)
.map(|id| *id)
.collect();
let outgoing: Vec<RoadID> = i.outgoing_roads
.iter()
.filter(|id| m.roads[id.0].lane_type == LaneType::Driving)
.map(|id| *id)
.collect();
// TODO: Figure out why this happens in the huge map
if incoming.is_empty() {
println!("WARNING: intersection {:?} has no incoming roads", i);
return Vec::new();
}
if outgoing.is_empty() {
println!("WARNING: intersection {:?} has no outgoing roads", i);
return Vec::new();
}
let dead_end = incoming.len() == 1 && outgoing.len() == 1;
let mut result = Vec::new();
for src in &incoming {
let src_r = &m.roads[src.0];
for dst in &outgoing {
let dst_r = &m.roads[dst.0];
// Don't create U-turns unless it's a dead-end
if src_r.other_side == Some(dst_r.id) && !dead_end {
continue;
}
let id = TurnID(m.turns.len() + result.len());
result.push(Turn {
id,
parent: i.id,
src: *src,
dst: *dst,
src_pt: src_r.last_pt(),
dst_pt: dst_r.first_pt(),
});
}
}
result
}
fn trim_lines(roads: &mut Vec<Road>, i: &Intersection) {
use std::collections::hash_map::Entry;
let mut shortest_first_line: HashMap<RoadID, (Pt2D, Pt2D, si::Meter<f64>)> = HashMap::new();
let mut shortest_last_line: HashMap<RoadID, (Pt2D, Pt2D, si::Meter<f64>)> = HashMap::new();
fn update_shortest(
m: &mut HashMap<RoadID, (Pt2D, Pt2D, si::Meter<f64>)>,
r: RoadID,
l: (Pt2D, Pt2D),
) {
let new_len = geometry::euclid_dist(l);
match m.entry(r) {
Entry::Occupied(mut o) => {
if new_len < o.get().2 {
o.insert((l.0, l.1, new_len));
}
}
Entry::Vacant(v) => {
v.insert((l.0, l.1, new_len));
}
}
}
// For short first/last lines, this might not work well
for incoming in &i.incoming_roads {
for outgoing in &i.outgoing_roads {
let l1 = roads[incoming.0].last_line();
let l2 = roads[outgoing.0].first_line();
if let Some(hit) = geometry::line_segment_intersection(l1, l2) {
update_shortest(&mut shortest_last_line, *incoming, (l1.0, hit));
update_shortest(&mut shortest_first_line, *outgoing, (hit, l2.1));
}
}
}
// Apply the updates
for (id, triple) in &shortest_first_line {
roads[id.0].lane_center_pts[0] = triple.0;
roads[id.0].lane_center_pts[1] = triple.1;
}
for (id, triple) in &shortest_last_line {
let len = roads[id.0].lane_center_pts.len();
roads[id.0].lane_center_pts[len - 2] = triple.0;
roads[id.0].lane_center_pts[len - 1] = triple.1;
}
}
// (original direction, reversed direction)
fn get_lanes(r: &raw_data::Road) -> (Vec<LaneType>, Vec<LaneType>) {
let oneway = r.osm_tags.get("oneway") == Some(&"yes".to_string());
// These seem to represent weird roundabouts
let junction = r.osm_tags.get("junction") == Some(&"yes".to_string());
let big_road = r.osm_tags.get("highway") == Some(&"primary".to_string())
|| r.osm_tags.get("highway") == Some(&"secondary".to_string());
// TODO debugging convenience
let only_roads_for_debugging = false;
if junction {
return (vec![LaneType::Driving], Vec::new());
}
let num_driving_lanes = if big_road { 2 } else { 1 };
let driving_lanes: Vec<LaneType> = iter::repeat(LaneType::Driving)
.take(num_driving_lanes)
.collect();
if only_roads_for_debugging {
if oneway {
return (driving_lanes, Vec::new());
} else {
return (driving_lanes.clone(), driving_lanes);
}
}
let mut full_side = driving_lanes;
full_side.push(LaneType::Parking);
full_side.push(LaneType::Sidewalk);
if oneway {
(full_side, vec![LaneType::Sidewalk])
} else {
(full_side.clone(), full_side)
}
}
struct LaneSpec {
lane_type: LaneType,
offset: u8,
reverse_pts: bool,
offset_for_other_id: Option<isize>,
}
fn get_lane_specs(r: &raw_data::Road) -> Vec<LaneSpec> {
let mut specs: Vec<LaneSpec> = Vec::new();
let (side1_types, side2_types) = get_lanes(r);
for (idx, lane_type) in side1_types.iter().enumerate() {
// TODO this might be a bit wrong. add unit tests. :)
let offset_for_other_id = if *lane_type != LaneType::Driving {
None
} else if !side2_types.contains(&LaneType::Driving) {
None
} else if side1_types == side2_types {
Some(side1_types.len() as isize)
} else {
panic!("get_lane_specs case not handled yet");
};
specs.push(LaneSpec {
offset_for_other_id,
lane_type: *lane_type,
offset: idx as u8,
reverse_pts: false,
});
}
for (idx, lane_type) in side2_types.iter().enumerate() {
let offset_for_other_id = if *lane_type != LaneType::Driving {
None
} else {
Some(-1 * (side1_types.len() as isize))
};
specs.push(LaneSpec {
offset_for_other_id,
lane_type: *lane_type,
offset: idx as u8,
reverse_pts: true,
});
}
specs
}