split parking lot code from building code, for #231

This commit is contained in:
Dustin Carlino 2020-07-25 07:09:43 -07:00
parent a4a545cc79
commit eb4acdc2f6
6 changed files with 255 additions and 227 deletions

8
Cargo.lock generated
View File

@ -445,8 +445,8 @@ dependencies = [
[[package]]
name = "contour"
version = "0.1.0"
source = "git+https://github.com/dabreegster/contour-rs#356b770587504de761a5ded7bb7684e779232ae7"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"geojson 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1044,7 +1044,7 @@ dependencies = [
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"colorous 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"contour 0.1.0 (git+https://github.com/dabreegster/contour-rs)",
"contour 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"enumset 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ezgui 0.1.0",
@ -3963,7 +3963,7 @@ dependencies = [
"checksum colorous 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebeb47d6d3334179ee49ef9a1f3a03d177450b25705854d92e2e1d128b49c736"
"checksum const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a"
"checksum const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a"
"checksum contour 0.1.0 (git+https://github.com/dabreegster/contour-rs)" = "<none>"
"checksum contour 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32505c91a846e14e02e458fc4ecdb6755858bfaf876717968d35cf623f6f554c"
"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
"checksum core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59e78b2e0aaf43f08e7ae0d6bc96895ef72ff0921c7d4ff4762201b2dba376dd"

View File

@ -17,7 +17,7 @@ built = { version = "0.4.2", optional = true, features=["chrono"] }
chrono = "0.4.10"
clipboard = { version = "0.5.0", optional = true }
colorous = "1.0.1"
contour = { git = "https://github.com/dabreegster/contour-rs" }
contour = "0.2.0"
downcast-rs = "1.1.1"
enumset = "1.0.0"
ezgui = { path = "../ezgui", default-features=false }

View File

@ -183,7 +183,7 @@ impl CommuterPatterns {
BlockSelection::NothingSelected => None,
};
return match base_block_id {
match base_block_id {
None => (ctx.upload(batch), None),
Some(base_block_id) => {
let base_block = &self.blocks[base_block_id];
@ -290,7 +290,7 @@ impl CommuterPatterns {
};
(ctx.upload(batch), Some(composite_data))
}
};
}
}
fn redraw_composite(&mut self, state: Option<&CompositeState>, ctx: &mut EventCtx, app: &App) {

View File

@ -1,11 +1,10 @@
use crate::make::match_points_to_lanes;
use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot};
use crate::raw::{OriginalBuilding, RawBuilding};
use crate::{
osm, Building, BuildingID, BuildingType, FrontPath, LaneID, LaneType, Map, OffstreetParking,
ParkingLot, ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH,
};
use abstutil::{Tags, Timer};
use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring};
use geom::{Distance, HashablePt2D, Line, PolyLine, Polygon};
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use std::collections::{BTreeMap, BTreeSet, HashSet};
@ -127,145 +126,6 @@ pub fn make_all_buildings(
results
}
pub fn make_all_parking_lots(
input: &Vec<RawParkingLot>,
aisles: &Vec<Vec<Pt2D>>,
map: &Map,
timer: &mut Timer,
) -> Vec<ParkingLot> {
timer.start("convert parking lots");
let mut center_per_lot: Vec<HashablePt2D> = Vec::new();
let mut query: HashSet<HashablePt2D> = HashSet::new();
for lot in input {
let center = lot.polygon.center().to_hashable();
center_per_lot.push(center);
query.insert(center);
}
let sidewalk_buffer = Distance::meters(7.5);
let driveway_buffer = Distance::meters(7.0);
let sidewalk_pts = match_points_to_lanes(
map.get_bounds(),
query,
map.all_lanes(),
|l| l.is_sidewalk(),
sidewalk_buffer,
Distance::meters(1000.0),
timer,
);
let mut results = Vec::new();
timer.start_iter("create parking lot driveways", center_per_lot.len());
for (lot_center, orig) in center_per_lot.into_iter().zip(input.iter()) {
timer.next();
// TODO Refactor this
if let Some(sidewalk_pos) = sidewalk_pts.get(&lot_center) {
let sidewalk_line = match Line::new(lot_center.to_pt2d(), sidewalk_pos.pt(map)) {
Some(l) => trim_path(&orig.polygon, l),
None => {
timer.warn(format!(
"Skipping parking lot {} because front path has 0 length",
orig.osm_id
));
continue;
}
};
// Can this lot have a driveway? If it's not next to a driving lane, then no.
let mut driveway: Option<(PolyLine, Position)> = None;
let sidewalk_lane = sidewalk_pos.lane();
if let Ok(driving_lane) = map
.get_parent(sidewalk_lane)
.find_closest_lane(sidewalk_lane, vec![LaneType::Driving])
{
let driving_pos = sidewalk_pos.equiv_pos(driving_lane, Distance::ZERO, map);
if driving_pos.dist_along() > driveway_buffer
&& map.get_l(driving_lane).length() - driving_pos.dist_along() > driveway_buffer
{
driveway = Some((
PolyLine::must_new(vec![
sidewalk_line.pt1(),
sidewalk_line.pt2(),
driving_pos.pt(map),
]),
driving_pos,
));
}
}
if let Some((driveway_line, driving_pos)) = driveway {
let id = ParkingLotID(results.len());
results.push(ParkingLot {
id,
polygon: orig.polygon.clone(),
aisles: Vec::new(),
osm_id: orig.osm_id,
spots: Vec::new(),
driveway_line,
driving_pos,
sidewalk_line,
sidewalk_pos: *sidewalk_pos,
});
} else {
timer.warn(format!(
"Parking lot from OSM way {} can't have a driveway.",
orig.osm_id
));
}
}
}
timer.note(format!(
"Discarded {} parking lots that weren't close enough to a sidewalk",
input.len() - results.len()
));
let mut closest: FindClosest<ParkingLotID> = FindClosest::new(map.get_bounds());
for lot in &results {
closest.add(lot.id, lot.polygon.points());
}
timer.start_iter("match parking aisles", aisles.len());
for pts in aisles {
timer.next();
// Use the center of all the aisle points to match it to a lot
let candidates: Vec<ParkingLotID> = closest
.all_close_pts(Pt2D::center(&pts), Distance::meters(500.0))
.into_iter()
.map(|(id, _, _)| id)
.collect();
let (polylines, rings) = Ring::split_points(pts).unwrap();
'PL: for pl in polylines {
for id in &candidates {
let lot = &mut results[id.0];
for segment in lot.polygon.clip_polyline(&pl) {
lot.aisles.push(segment);
continue 'PL;
}
}
}
'RING: for ring in rings {
for id in &candidates {
let lot = &mut results[id.0];
for segment in lot.polygon.clip_ring(&ring) {
lot.aisles.push(segment);
continue 'RING;
}
}
}
}
timer.start_iter("generate parking lot spots", results.len());
for lot in results.iter_mut() {
timer.next();
lot.spots = infer_spots(&lot.polygon, &lot.aisles);
}
timer.stop("convert parking lots");
results
}
// Adjust the path to start on the building's border, not center
fn trim_path(poly: &Polygon, path: Line) -> Line {
for bldg_line in poly.points().windows(2) {
@ -289,82 +149,6 @@ fn get_address(tags: &Tags, sidewalk: LaneID, map: &Map) -> String {
}
}
fn infer_spots(lot_polygon: &Polygon, aisles: &Vec<Vec<Pt2D>>) -> Vec<(Pt2D, Angle)> {
let mut spots = Vec::new();
let mut finalized_lines = Vec::new();
for aisle in aisles {
let aisle_thickness = NORMAL_LANE_THICKNESS / 2.0;
let pl = PolyLine::unchecked_new(aisle.clone());
for rotate in vec![90.0, -90.0] {
// Blindly generate all of the lines
let lines = {
let mut lines = Vec::new();
let mut start = Distance::ZERO;
while start + NORMAL_LANE_THICKNESS < pl.length() {
let (pt, angle) = pl.must_dist_along(start);
start += NORMAL_LANE_THICKNESS;
let theta = angle.rotate_degs(rotate);
lines.push(Line::must_new(
pt.project_away(aisle_thickness / 2.0, theta),
pt.project_away(aisle_thickness / 2.0 + PARKING_LOT_SPOT_LENGTH, theta),
));
}
lines
};
for pair in lines.windows(2) {
let l1 = &pair[0];
let l2 = &pair[1];
let back = Line::must_new(l1.pt2(), l2.pt2());
if l1.intersection(&l2).is_none()
&& l1.angle().approx_eq(l2.angle(), 5.0)
&& line_valid(lot_polygon, aisles, l1, &finalized_lines)
&& line_valid(lot_polygon, aisles, l2, &finalized_lines)
&& line_valid(lot_polygon, aisles, &back, &finalized_lines)
{
let avg_angle = (l1.angle() + l2.angle()) / 2.0;
spots.push((back.middle().unwrap(), avg_angle.opposite()));
finalized_lines.push(l1.clone());
finalized_lines.push(l2.clone());
finalized_lines.push(back);
}
}
}
}
spots
}
fn line_valid(
lot_polygon: &Polygon,
aisles: &Vec<Vec<Pt2D>>,
line: &Line,
finalized_lines: &Vec<Line>,
) -> bool {
// Don't leak out of the parking lot
// TODO Entire line
if !lot_polygon.contains_pt(line.pt1()) || !lot_polygon.contains_pt(line.pt2()) {
return false;
}
// Don't let this line hit another line
if finalized_lines.iter().any(|other| line.crosses(other)) {
return false;
}
// Don't hit an aisle
if aisles.iter().any(|pts| {
PolyLine::unchecked_new(pts.clone())
.intersection(&line.to_polyline())
.is_some()
}) {
return false;
}
true
}
fn classify_bldg(
tags: Tags,
amenities: &BTreeSet<(String, String)>,

View File

@ -1,6 +1,7 @@
mod bridges;
mod buildings;
pub mod initial;
mod parking_lots;
mod remove_disconnected;
pub mod traffic_signals;
mod transit;
@ -280,8 +281,12 @@ impl Map {
map.lanes[lane.0].building_paths = bldgs;
}
map.parking_lots =
buildings::make_all_parking_lots(&raw.parking_lots, &raw.parking_aisles, &map, timer);
map.parking_lots = parking_lots::make_all_parking_lots(
&raw.parking_lots,
&raw.parking_aisles,
&map,
timer,
);
map.zones = Zone::make_all(&map);

View File

@ -0,0 +1,239 @@
use crate::make::match_points_to_lanes;
use crate::raw::RawParkingLot;
use crate::{
LaneType, Map, ParkingLot, ParkingLotID, Position, NORMAL_LANE_THICKNESS,
PARKING_LOT_SPOT_LENGTH,
};
use abstutil::Timer;
use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring};
use std::collections::HashSet;
pub fn make_all_parking_lots(
input: &Vec<RawParkingLot>,
aisles: &Vec<Vec<Pt2D>>,
map: &Map,
timer: &mut Timer,
) -> Vec<ParkingLot> {
timer.start("convert parking lots");
let mut center_per_lot: Vec<HashablePt2D> = Vec::new();
let mut query: HashSet<HashablePt2D> = HashSet::new();
for lot in input {
let center = lot.polygon.center().to_hashable();
center_per_lot.push(center);
query.insert(center);
}
let sidewalk_buffer = Distance::meters(7.5);
let driveway_buffer = Distance::meters(7.0);
let sidewalk_pts = match_points_to_lanes(
map.get_bounds(),
query,
map.all_lanes(),
|l| l.is_sidewalk(),
sidewalk_buffer,
Distance::meters(1000.0),
timer,
);
let mut results = Vec::new();
timer.start_iter("create parking lot driveways", center_per_lot.len());
for (lot_center, orig) in center_per_lot.into_iter().zip(input.iter()) {
timer.next();
// TODO Refactor this
if let Some(sidewalk_pos) = sidewalk_pts.get(&lot_center) {
let sidewalk_line = match Line::new(lot_center.to_pt2d(), sidewalk_pos.pt(map)) {
Some(l) => trim_path(&orig.polygon, l),
None => {
timer.warn(format!(
"Skipping parking lot {} because front path has 0 length",
orig.osm_id
));
continue;
}
};
// Can this lot have a driveway? If it's not next to a driving lane, then no.
let mut driveway: Option<(PolyLine, Position)> = None;
let sidewalk_lane = sidewalk_pos.lane();
if let Ok(driving_lane) = map
.get_parent(sidewalk_lane)
.find_closest_lane(sidewalk_lane, vec![LaneType::Driving])
{
let driving_pos = sidewalk_pos.equiv_pos(driving_lane, Distance::ZERO, map);
if driving_pos.dist_along() > driveway_buffer
&& map.get_l(driving_lane).length() - driving_pos.dist_along() > driveway_buffer
{
driveway = Some((
PolyLine::must_new(vec![
sidewalk_line.pt1(),
sidewalk_line.pt2(),
driving_pos.pt(map),
]),
driving_pos,
));
}
}
if let Some((driveway_line, driving_pos)) = driveway {
let id = ParkingLotID(results.len());
results.push(ParkingLot {
id,
polygon: orig.polygon.clone(),
aisles: Vec::new(),
osm_id: orig.osm_id,
spots: Vec::new(),
driveway_line,
driving_pos,
sidewalk_line,
sidewalk_pos: *sidewalk_pos,
});
} else {
timer.warn(format!(
"Parking lot from OSM way {} can't have a driveway.",
orig.osm_id
));
}
}
}
timer.note(format!(
"Discarded {} parking lots that weren't close enough to a sidewalk",
input.len() - results.len()
));
let mut closest: FindClosest<ParkingLotID> = FindClosest::new(map.get_bounds());
for lot in &results {
closest.add(lot.id, lot.polygon.points());
}
timer.start_iter("match parking aisles", aisles.len());
for pts in aisles {
timer.next();
// Use the center of all the aisle points to match it to a lot
let candidates: Vec<ParkingLotID> = closest
.all_close_pts(Pt2D::center(&pts), Distance::meters(500.0))
.into_iter()
.map(|(id, _, _)| id)
.collect();
let (polylines, rings) = Ring::split_points(pts).unwrap();
'PL: for pl in polylines {
for id in &candidates {
let lot = &mut results[id.0];
for segment in lot.polygon.clip_polyline(&pl) {
lot.aisles.push(segment);
continue 'PL;
}
}
}
'RING: for ring in rings {
for id in &candidates {
let lot = &mut results[id.0];
for segment in lot.polygon.clip_ring(&ring) {
lot.aisles.push(segment);
continue 'RING;
}
}
}
}
timer.start_iter("generate parking lot spots", results.len());
for lot in results.iter_mut() {
timer.next();
lot.spots = infer_spots(&lot.polygon, &lot.aisles);
}
timer.stop("convert parking lots");
results
}
// Adjust the path to start on the building's border, not center
fn trim_path(poly: &Polygon, path: Line) -> Line {
for bldg_line in poly.points().windows(2) {
if let Some(l1) = Line::new(bldg_line[0], bldg_line[1]) {
if let Some(hit) = l1.intersection(&path) {
if let Some(l2) = Line::new(hit, path.pt2()) {
return l2;
}
}
}
}
// Just give up
path
}
fn infer_spots(lot_polygon: &Polygon, aisles: &Vec<Vec<Pt2D>>) -> Vec<(Pt2D, Angle)> {
let mut spots = Vec::new();
let mut finalized_lines = Vec::new();
for aisle in aisles {
let aisle_thickness = NORMAL_LANE_THICKNESS / 2.0;
let pl = PolyLine::unchecked_new(aisle.clone());
for rotate in vec![90.0, -90.0] {
// Blindly generate all of the lines
let lines = {
let mut lines = Vec::new();
let mut start = Distance::ZERO;
while start + NORMAL_LANE_THICKNESS < pl.length() {
let (pt, angle) = pl.must_dist_along(start);
start += NORMAL_LANE_THICKNESS;
let theta = angle.rotate_degs(rotate);
lines.push(Line::must_new(
pt.project_away(aisle_thickness / 2.0, theta),
pt.project_away(aisle_thickness / 2.0 + PARKING_LOT_SPOT_LENGTH, theta),
));
}
lines
};
for pair in lines.windows(2) {
let l1 = &pair[0];
let l2 = &pair[1];
let back = Line::must_new(l1.pt2(), l2.pt2());
if l1.intersection(&l2).is_none()
&& l1.angle().approx_eq(l2.angle(), 5.0)
&& line_valid(lot_polygon, aisles, l1, &finalized_lines)
&& line_valid(lot_polygon, aisles, l2, &finalized_lines)
&& line_valid(lot_polygon, aisles, &back, &finalized_lines)
{
let avg_angle = (l1.angle() + l2.angle()) / 2.0;
spots.push((back.middle().unwrap(), avg_angle.opposite()));
finalized_lines.push(l1.clone());
finalized_lines.push(l2.clone());
finalized_lines.push(back);
}
}
}
}
spots
}
fn line_valid(
lot_polygon: &Polygon,
aisles: &Vec<Vec<Pt2D>>,
line: &Line,
finalized_lines: &Vec<Line>,
) -> bool {
// Don't leak out of the parking lot
// TODO Entire line
if !lot_polygon.contains_pt(line.pt1()) || !lot_polygon.contains_pt(line.pt2()) {
return false;
}
// Don't let this line hit another line
if finalized_lines.iter().any(|other| line.crosses(other)) {
return false;
}
// Don't hit an aisle
if aisles.iter().any(|pts| {
PolyLine::unchecked_new(pts.clone())
.intersection(&line.to_polyline())
.is_some()
}) {
return false;
}
true
}