using quadtrees to MASSIVELY speed up sidewalk matching

This commit is contained in:
Dustin Carlino 2018-10-28 13:06:43 -07:00
parent 65d8c92f63
commit 15da2fe9cd
10 changed files with 159 additions and 102 deletions

View File

@ -3,7 +3,7 @@
use aabb_quadtree::geom::{Point, Rect};
use aabb_quadtree::QuadTree;
use control::ControlMap;
use geom::{Bounds, LonLat, Pt2D};
use geom::{Bounds, Pt2D};
use kml::{ExtraShape, ExtraShapeID};
use map_model::{
AreaID, BuildingID, BusStopID, IntersectionID, Lane, LaneID, Map, ParcelID, Turn, TurnID,
@ -83,17 +83,8 @@ impl DrawMap {
}
let areas: Vec<DrawArea> = map.all_areas().iter().map(|a| DrawArea::new(a)).collect();
// min_y here due to the wacky y inversion
let bounds = map.get_gps_bounds();
let max_screen_pt =
Pt2D::from_gps(LonLat::new(bounds.max_x, bounds.min_y), &bounds).unwrap();
let map_bbox = Rect {
top_left: Point { x: 0.0, y: 0.0 },
bottom_right: Point {
x: max_screen_pt.x() as f32,
y: max_screen_pt.y() as f32,
},
};
let bounds = map.get_bounds();
let map_bbox = get_bbox(bounds);
let mut quadtree = QuadTree::default(map_bbox);
// TODO use iter chain if everything was boxed as a renderable...
@ -129,7 +120,7 @@ impl DrawMap {
bus_stops,
areas,
center_pt: Pt2D::new(max_screen_pt.x() / 2.0, max_screen_pt.y() / 2.0),
center_pt: Pt2D::new(bounds.max_x / 2.0, bounds.max_y / 2.0),
quadtree,
}

View File

@ -1,7 +1,7 @@
use aabb_quadtree::geom::{Point, Rect};
use aabb_quadtree::QuadTree;
use ezgui::{Color, GfxCtx};
use geom::{Bounds, Line, LonLat, Polygon, Pt2D};
use geom::{Bounds, Line, Polygon, Pt2D};
use map_model::{Building, BuildingID, Map, Road, RoadID, LANE_THICKNESS};
// black
@ -26,15 +26,12 @@ pub struct DrawMap {
impl DrawMap {
pub fn new(map: Map) -> DrawMap {
// TODO This stuff is common!
// min_y here due to the wacky y inversion
let bounds = map.get_gps_bounds();
let max_screen_pt =
Pt2D::from_gps(LonLat::new(bounds.max_x, bounds.min_y), &bounds).unwrap();
let bounds = map.get_bounds();
let map_bbox = Rect {
top_left: Point { x: 0.0, y: 0.0 },
bottom_right: Point {
x: max_screen_pt.x() as f32,
y: max_screen_pt.y() as f32,
x: bounds.max_x as f32,
y: bounds.max_y as f32,
},
};

View File

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Dustin Carlino <dabreegster@gmail.com>"]
[dependencies]
aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" }
dimensioned = { git = "https://github.com/paholg/dimensioned", rev = "0e1076ebfa5128d1ee544bdc9754c948987b6fe3", features = ["serde"] }
flame = "0.2.2"

View File

@ -1,3 +1,4 @@
extern crate aabb_quadtree;
extern crate abstutil;
extern crate dimensioned;
extern crate flame;

View File

@ -9,6 +9,7 @@ use {Building, BuildingID, FrontPath, Lane};
pub fn make_all_buildings(
results: &mut Vec<Building>,
input: &Vec<raw_data::Building>,
gps_bounds: &Bounds,
bounds: &Bounds,
lanes: &Vec<Lane>,
) {
@ -21,7 +22,7 @@ pub fn make_all_buildings(
let pts = b
.points
.iter()
.map(|coord| Pt2D::from_gps(*coord, bounds).unwrap())
.map(|coord| Pt2D::from_gps(*coord, gps_bounds).unwrap())
.collect();
let center: HashablePt2D = Pt2D::center(&pts).into();
pts_per_bldg.push(pts);
@ -29,34 +30,31 @@ pub fn make_all_buildings(
query.insert(center);
}
let sidewalk_pts = find_sidewalk_points(query, lanes);
// Skip buildings that're too far away from their sidewalk
let sidewalk_pts = find_sidewalk_points(bounds, query, lanes, 100.0 * si::M);
let mut progress = Progress::new("create building front paths", pts_per_bldg.len());
for (idx, points) in pts_per_bldg.into_iter().enumerate() {
progress.next();
let bldg_center = center_per_bldg[idx];
let (sidewalk, dist_along) = sidewalk_pts[&bldg_center];
let (sidewalk_pt, _) = lanes[sidewalk.0].dist_along(dist_along);
let line = trim_front_path(&points, Line::new(bldg_center.into(), sidewalk_pt));
if let Some((sidewalk, dist_along)) = sidewalk_pts.get(&bldg_center) {
let (sidewalk_pt, _) = lanes[sidewalk.0].dist_along(*dist_along);
let line = trim_front_path(&points, Line::new(bldg_center.into(), sidewalk_pt));
// Trim buildings that are too far away from their sidewalk
if line.length() > 100.0 * si::M {
continue;
let id = BuildingID(results.len());
results.push(Building {
id,
points,
osm_tags: input[idx].osm_tags.clone(),
osm_way_id: input[idx].osm_way_id,
front_path: FrontPath {
bldg: id,
sidewalk: *sidewalk,
line,
dist_along_sidewalk: *dist_along,
},
});
}
let id = BuildingID(results.len());
results.push(Building {
id,
points,
osm_tags: input[idx].osm_tags.clone(),
osm_way_id: input[idx].osm_way_id,
front_path: FrontPath {
bldg: id,
sidewalk: sidewalk,
line,
dist_along_sidewalk: dist_along,
},
});
}
let discarded = input.len() - results.len();

View File

@ -12,13 +12,14 @@ pub fn make_bus_stops(
lanes: &mut Vec<Lane>,
roads: &Vec<Road>,
bus_routes: &Vec<gtfs::Route>,
gps_bounds: &Bounds,
bounds: &Bounds,
) -> (BTreeMap<BusStopID, BusStop>, Vec<BusRoute>) {
let mut bus_stop_pts: HashSet<HashablePt2D> = HashSet::new();
let mut route_lookups: MultiMap<String, HashablePt2D> = MultiMap::new();
for route in bus_routes {
for gps in &route.stops {
if let Some(pt) = Pt2D::from_gps(*gps, bounds) {
if let Some(pt) = Pt2D::from_gps(*gps, gps_bounds) {
let hash_pt: HashablePt2D = pt.into();
bus_stop_pts.insert(hash_pt);
route_lookups.insert(route.name.to_string(), hash_pt);
@ -27,7 +28,9 @@ pub fn make_bus_stops(
}
let mut stops_per_sidewalk: MultiMap<LaneID, (si::Meter<f64>, HashablePt2D)> = MultiMap::new();
for (pt, (lane, dist_along)) in find_sidewalk_points(bus_stop_pts, lanes).iter() {
for (pt, (lane, dist_along)) in
find_sidewalk_points(bounds, bus_stop_pts, lanes, 10.0 * si::M).iter()
{
stops_per_sidewalk.insert(*lane, (*dist_along, *pt));
}
let mut point_to_stop_id: HashMap<HashablePt2D, BusStopID> = HashMap::new();

View File

@ -1,5 +1,5 @@
use dimensioned::si;
use geom::{Bounds, HashablePt2D, Line, Pt2D};
use geom::{Bounds, HashablePt2D, Pt2D};
use make::sidewalk_finder::find_sidewalk_points;
use raw_data;
use std::collections::HashSet;
@ -8,6 +8,7 @@ use {Lane, Parcel, ParcelID};
pub fn make_all_parcels(
results: &mut Vec<Parcel>,
input: &Vec<raw_data::Parcel>,
gps_bounds: &Bounds,
bounds: &Bounds,
lanes: &Vec<Lane>,
) {
@ -18,7 +19,7 @@ pub fn make_all_parcels(
let pts = p
.points
.iter()
.map(|coord| Pt2D::from_gps(*coord, bounds).unwrap())
.map(|coord| Pt2D::from_gps(*coord, gps_bounds).unwrap())
.collect();
let center: HashablePt2D = Pt2D::center(&pts).into();
pts_per_parcel.push(pts);
@ -26,23 +27,18 @@ pub fn make_all_parcels(
query.insert(center);
}
let sidewalk_pts = find_sidewalk_points(query, lanes);
// Trim parcels that are too far away from the nearest sidewalk
let sidewalk_pts = find_sidewalk_points(bounds, query, lanes, 100.0 * si::M);
for (idx, center) in center_per_parcel.into_iter().enumerate() {
let (sidewalk, dist_along) = sidewalk_pts[&center];
let (sidewalk_pt, _) = lanes[sidewalk.0].dist_along(dist_along);
let line = Line::new(center.into(), sidewalk_pt);
// Trim parcels that are too far away from the nearest sidewalk
if line.length() > 100.0 * si::M {
continue;
if sidewalk_pts.contains_key(&center) {
let id = ParcelID(results.len());
results.push(Parcel {
id,
points: pts_per_parcel[idx].clone(),
block: input[idx].block,
});
}
let id = ParcelID(results.len());
results.push(Parcel {
id,
points: pts_per_parcel[idx].clone(),
block: input[idx].block,
});
}
let discarded = input.len() - results.len();
if discarded > 0 {

View File

@ -1,50 +1,70 @@
use aabb_quadtree::geom::{Point, Rect};
use aabb_quadtree::QuadTree;
use abstutil::Progress;
use dimensioned::si;
use geo;
use geo::prelude::{ClosestPoint, EuclideanDistance};
use geom::{HashablePt2D, Pt2D};
use geom::{Bounds, HashablePt2D, Pt2D};
use ordered_float::NotNaN;
use std::collections::{HashMap, HashSet};
use {Lane, LaneID};
// If the result doesn't contain a requested point, then there was no matching sidewalk close
// enough.
pub fn find_sidewalk_points(
bounds: &Bounds,
pts: HashSet<HashablePt2D>,
lanes: &Vec<Lane>,
max_dist_away: si::Meter<f64>,
) -> HashMap<HashablePt2D, (LaneID, si::Meter<f64>)> {
// Get LineStrings of all lanes once.
// Convert all sidewalks to LineStrings and index them with a quadtree.
let mut lane_lines_quadtree: QuadTree<usize> = QuadTree::default(get_bbox(bounds));
let mut lane_lines: Vec<(LaneID, geo::LineString<f64>)> = Vec::new();
let mut progress = Progress::new("lanes to LineStrings", lanes.len());
let line_strings: Vec<(LaneID, geo::LineString<f64>)> = lanes
.iter()
.filter_map(|l| {
progress.next();
if l.is_sidewalk() {
Some((l.id, lane_to_line_string(l)))
} else {
None
}
}).collect();
for l in lanes {
progress.next();
if l.is_sidewalk() {
lane_lines.push((l.id, lane_to_line_string(l)));
lane_lines_quadtree.insert_with_box(lane_lines.len() - 1, lane_to_rect(l));
}
}
// For each point, find the closest point to any sidewalk
// For each point, find the closest point to any sidewalk, using the quadtree to prune the
// search.
let mut results: HashMap<HashablePt2D, (LaneID, si::Meter<f64>)> = HashMap::new();
let mut progress = Progress::new("find closest sidewalk point", pts.len());
for query_pt in pts {
progress.next();
let query_geo_pt = geo::Point::new(query_pt.x(), query_pt.y());
let (sidewalk, raw_pt) = line_strings
.iter()
.filter_map(|(id, lines)| {
let query_bbox = Rect {
top_left: Point {
x: (query_pt.x() - max_dist_away.value_unsafe) as f32,
y: (query_pt.y() - max_dist_away.value_unsafe) as f32,
},
bottom_right: Point {
x: (query_pt.x() + max_dist_away.value_unsafe) as f32,
y: (query_pt.y() + max_dist_away.value_unsafe) as f32,
},
};
if let Some((sidewalk, raw_pt)) = lane_lines_quadtree
.query(query_bbox)
.into_iter()
.filter_map(|(idx, _, _)| {
let (id, lines) = &lane_lines[*idx];
if let geo::Closest::SinglePoint(pt) = lines.closest_point(&query_geo_pt) {
Some((id, pt))
Some((*id, pt))
} else {
None
}
}).min_by_key(|(_, pt)| NotNaN::new(pt.euclidean_distance(&query_geo_pt)).unwrap())
.unwrap();
let sidewalk_pt = Pt2D::new(raw_pt.x(), raw_pt.y());
if let Some(dist_along) = lanes[sidewalk.0].dist_along_of_point(sidewalk_pt) {
results.insert(query_pt.into(), (*sidewalk, dist_along));
} else {
panic!("{} isn't on {} according to dist_along_of_point, even though closest_point thinks it is.\n{}", sidewalk_pt, sidewalk, lanes[sidewalk.0].lane_center_pts);
{
let sidewalk_pt = Pt2D::new(raw_pt.x(), raw_pt.y());
if let Some(dist_along) = lanes[sidewalk.0].dist_along_of_point(sidewalk_pt) {
results.insert(query_pt.into(), (sidewalk, dist_along));
} else {
panic!("{} isn't on {} according to dist_along_of_point, even though closest_point thinks it is.\n{}", sidewalk_pt, sidewalk, lanes[sidewalk.0].lane_center_pts);
}
}
}
results
@ -59,3 +79,24 @@ fn lane_to_line_string(l: &Lane) -> geo::LineString<f64> {
.collect();
pts.into()
}
fn lane_to_rect(l: &Lane) -> Rect {
let mut b = Bounds::new();
for pt in l.lane_center_pts.points() {
b.update_pt(*pt);
}
get_bbox(&b)
}
fn get_bbox(b: &Bounds) -> Rect {
Rect {
top_left: Point {
x: b.min_x as f32,
y: b.min_y as f32,
},
bottom_right: Point {
x: b.max_x as f32,
y: b.max_y as f32,
},
}
}

View File

@ -4,7 +4,7 @@ use abstutil;
use abstutil::{Error, Progress};
use edits::RoadEdits;
use flame;
use geom::{Bounds, HashablePt2D, PolyLine, Pt2D};
use geom::{Bounds, HashablePt2D, LonLat, PolyLine, Pt2D};
use make;
use raw_data;
use std::collections::{BTreeMap, BTreeSet, HashMap};
@ -28,7 +28,7 @@ pub struct Map {
areas: Vec<Area>,
// TODO maybe dont need to retain GPS stuff later
bounds: Bounds,
gps_bounds: Bounds,
name: String,
road_edits: RoadEdits,
@ -55,11 +55,12 @@ impl Map {
}
pub fn create_from_raw(name: String, data: raw_data::Map, road_edits: RoadEdits) -> Map {
let bounds = data.get_gps_bounds();
let gps_bounds = data.get_gps_bounds();
let bounds = gps_to_map_bounds(&gps_bounds);
let mut m = Map {
name,
road_edits,
bounds,
gps_bounds,
roads: Vec::new(),
lanes: Vec::new(),
intersections: Vec::new(),
@ -75,7 +76,7 @@ impl Map {
for (idx, i) in data.intersections.iter().enumerate() {
let id = IntersectionID(idx);
let pt = Pt2D::from_gps(i.point, &bounds).unwrap();
let pt = Pt2D::from_gps(i.point, &gps_bounds).unwrap();
m.intersections.push(Intersection {
id,
point: pt,
@ -94,7 +95,7 @@ impl Map {
let road_center_pts = PolyLine::new(
r.points
.iter()
.map(|coord| Pt2D::from_gps(*coord, &bounds).unwrap())
.map(|coord| Pt2D::from_gps(*coord, &gps_bounds).unwrap())
.collect(),
);
@ -166,8 +167,13 @@ impl Map {
}
}
let (stops, routes) =
make::make_bus_stops(&mut m.lanes, &m.roads, &data.bus_routes, &bounds);
let (stops, routes) = make::make_bus_stops(
&mut m.lanes,
&m.roads,
&data.bus_routes,
&gps_bounds,
&bounds,
);
m.bus_stops = stops;
for i in &m.intersections {
@ -182,7 +188,13 @@ impl Map {
{
let _guard = flame::start_guard(format!("make {} buildings", data.buildings.len()));
make::make_all_buildings(&mut m.buildings, &data.buildings, &bounds, &m.lanes);
make::make_all_buildings(
&mut m.buildings,
&data.buildings,
&gps_bounds,
&bounds,
&m.lanes,
);
for b in &m.buildings {
m.lanes[b.front_path.sidewalk.0].building_paths.push(b.id);
}
@ -190,7 +202,13 @@ impl Map {
{
let _guard = flame::start_guard(format!("make {} parcels", data.parcels.len()));
make::make_all_parcels(&mut m.parcels, &data.parcels, &bounds, &m.lanes);
make::make_all_parcels(
&mut m.parcels,
&data.parcels,
&gps_bounds,
&bounds,
&m.lanes,
);
}
for (idx, a) in data.areas.iter().enumerate() {
@ -200,7 +218,7 @@ impl Map {
points: a
.points
.iter()
.map(|coord| Pt2D::from_gps(*coord, &bounds).unwrap())
.map(|coord| Pt2D::from_gps(*coord, &gps_bounds).unwrap())
.collect(),
osm_tags: a.osm_tags.clone(),
osm_way_id: a.osm_way_id,
@ -397,7 +415,11 @@ impl Map {
// TODO can we return a borrow?
pub fn get_gps_bounds(&self) -> Bounds {
self.bounds.clone()
self.gps_bounds.clone()
}
pub fn get_bounds(&self) -> Bounds {
gps_to_map_bounds(&self.gps_bounds)
}
pub fn get_driving_lane_from_bldg(&self, bldg: BuildingID) -> Result<LaneID, Error> {
@ -470,3 +492,12 @@ impl Map {
println!("Saved {}", path);
}
}
fn gps_to_map_bounds(gps: &Bounds) -> Bounds {
// min_y here due to the wacky y inversion
let max_screen_pt = Pt2D::from_gps(LonLat::new(gps.max_x, gps.min_y), gps).unwrap();
let mut b = Bounds::new();
b.update_pt(Pt2D::new(0.0, 0.0));
b.update_pt(max_screen_pt);
b
}

View File

@ -1,5 +1,5 @@
use abstutil;
use geom::{LonLat, Polygon, Pt2D};
use geom::{Polygon, Pt2D};
use map_model::{BuildingID, Map, RoadID};
use rand::Rng;
use std::collections::{BTreeSet, HashMap, HashSet};
@ -70,18 +70,16 @@ impl Neighborhood {
}
fn make_everywhere(map: &Map) -> Neighborhood {
// min_y here due to the wacky y inversion
let bounds = map.get_gps_bounds();
let max = Pt2D::from_gps(LonLat::new(bounds.max_x, bounds.min_y), &bounds).unwrap();
let bounds = map.get_bounds();
Neighborhood {
map_name: map.get_name().to_string(),
name: "_everywhere_".to_string(),
points: vec![
Pt2D::new(0.0, 0.0),
Pt2D::new(max.x(), 0.0),
max,
Pt2D::new(0.0, max.y()),
Pt2D::new(bounds.max_x, 0.0),
Pt2D::new(bounds.max_x, bounds.max_y),
Pt2D::new(0.0, bounds.max_y),
Pt2D::new(0.0, 0.0),
],
}