encode parking spots cheaply, compute up-front

This commit is contained in:
Dustin Carlino 2020-06-03 11:50:46 -07:00
parent b69d155812
commit ea6a5c0f9d
8 changed files with 162 additions and 110 deletions

View File

@ -364,16 +364,16 @@ data/input/seattle/sidewalks.bin,034dd47ab77902dbc81c0107f13d8965,https://www.dr
data/input/seattle/sidewalks.kml,94d385ba03ef1b57a5ba10965913ec6c,https://www.dropbox.com/s/vn8amar9xi6vbvh/sidewalks.kml.zip?dl=0 data/input/seattle/sidewalks.kml,94d385ba03ef1b57a5ba10965913ec6c,https://www.dropbox.com/s/vn8amar9xi6vbvh/sidewalks.kml.zip?dl=0
data/input/seattle/trips_2014.csv,d4a8e733045b28c0385fb81359d6df03,https://www.dropbox.com/s/5ppravwmk6bf20d/trips_2014.csv.zip?dl=0 data/input/seattle/trips_2014.csv,d4a8e733045b28c0385fb81359d6df03,https://www.dropbox.com/s/5ppravwmk6bf20d/trips_2014.csv.zip?dl=0
data/system/cities/seattle.bin,018968486daedf1b69a1b3be5a3749bf,https://www.dropbox.com/s/eupzog6iw7wtaas/seattle.bin.zip?dl=0 data/system/cities/seattle.bin,018968486daedf1b69a1b3be5a3749bf,https://www.dropbox.com/s/eupzog6iw7wtaas/seattle.bin.zip?dl=0
data/system/maps/ballard.bin,2abeb9837c0c2ecb89dfe7ec6c9223df,https://www.dropbox.com/s/u4rvz50she3yrk0/ballard.bin.zip?dl=0 data/system/maps/ballard.bin,78198be4e5a03a59c63cdd61846efc60,https://www.dropbox.com/s/u4rvz50she3yrk0/ballard.bin.zip?dl=0
data/system/maps/downtown.bin,f89e8ab3aefd66cc7d98adfd5071f6ce,https://www.dropbox.com/s/4do5cg4vc17lafo/downtown.bin.zip?dl=0 data/system/maps/downtown.bin,3555b5dea0a14c15e313026dc28f00ea,https://www.dropbox.com/s/4do5cg4vc17lafo/downtown.bin.zip?dl=0
data/system/maps/downtown_atx.bin,0902409dad2c3f933027ae3651936254,https://www.dropbox.com/s/5avnbkd4oxby2hs/downtown_atx.bin.zip?dl=0 data/system/maps/downtown_atx.bin,0902409dad2c3f933027ae3651936254,https://www.dropbox.com/s/5avnbkd4oxby2hs/downtown_atx.bin.zip?dl=0
data/system/maps/huge_austin.bin,6a891b6a7597fd7f44e0229dd002773a,https://www.dropbox.com/s/khy0m6v9yt0gjnt/huge_austin.bin.zip?dl=0 data/system/maps/huge_austin.bin,6a891b6a7597fd7f44e0229dd002773a,https://www.dropbox.com/s/khy0m6v9yt0gjnt/huge_austin.bin.zip?dl=0
data/system/maps/huge_seattle.bin,53b71c4d1b723f2a61d2bf29cc3ff0ab,https://www.dropbox.com/s/btvr3qajshnivhb/huge_seattle.bin.zip?dl=0 data/system/maps/huge_seattle.bin,80e2d8068ada8624b9d8129c8728e74a,https://www.dropbox.com/s/btvr3qajshnivhb/huge_seattle.bin.zip?dl=0
data/system/maps/lakeslice.bin,6e02eaf499068e1a135a88fa38f8ae54,https://www.dropbox.com/s/99zi0gcbyvqrkud/lakeslice.bin.zip?dl=0 data/system/maps/lakeslice.bin,061a9c3e377bac0ae5db460c67c3eea9,https://www.dropbox.com/s/99zi0gcbyvqrkud/lakeslice.bin.zip?dl=0
data/system/maps/montlake.bin,e1991e7ae12df5e24a9e01d0409b184d,https://www.dropbox.com/s/zvhm2j5lavixxcr/montlake.bin.zip?dl=0 data/system/maps/montlake.bin,d58d14b9fbae8cfdf6c547fe140ff3fe,https://www.dropbox.com/s/zvhm2j5lavixxcr/montlake.bin.zip?dl=0
data/system/maps/mt_baker.bin,57a2761b4d1dfe9ab23c5ab9ba7e70b8,https://www.dropbox.com/s/cetje663p04cbgp/mt_baker.bin.zip?dl=0 data/system/maps/mt_baker.bin,09dd0d4220e1ebbed4fc730ed2081933,https://www.dropbox.com/s/cetje663p04cbgp/mt_baker.bin.zip?dl=0
data/system/maps/udistrict.bin,0e848ef2bd824d9656097e3f4ac39b19,https://www.dropbox.com/s/zqt2je8fadssz5j/udistrict.bin.zip?dl=0 data/system/maps/udistrict.bin,4de690facca9297f148396f3f09c3a6a,https://www.dropbox.com/s/zqt2je8fadssz5j/udistrict.bin.zip?dl=0
data/system/maps/west_seattle.bin,c9c394a9fb831bcbaa915bce81ae937f,https://www.dropbox.com/s/5pp1ik9l40yj3wh/west_seattle.bin.zip?dl=0 data/system/maps/west_seattle.bin,867b16afdf12c484d680bfbc73cbd919,https://www.dropbox.com/s/5pp1ik9l40yj3wh/west_seattle.bin.zip?dl=0
data/system/prebaked_results/lakeslice/weekday.bin,cbd73a7a14a8406a99cafac4e2eedf5b,https://www.dropbox.com/s/1c1sohvy50263wg/weekday.bin.zip?dl=0 data/system/prebaked_results/lakeslice/weekday.bin,cbd73a7a14a8406a99cafac4e2eedf5b,https://www.dropbox.com/s/1c1sohvy50263wg/weekday.bin.zip?dl=0
data/system/prebaked_results/montlake/car vs bike contention.bin,ac46a6b81a65431209075a7de5ed9227,https://www.dropbox.com/s/jefg0ikjy9dsrdd/car%20vs%20bike%20contention.bin.zip?dl=0 data/system/prebaked_results/montlake/car vs bike contention.bin,ac46a6b81a65431209075a7de5ed9227,https://www.dropbox.com/s/jefg0ikjy9dsrdd/car%20vs%20bike%20contention.bin.zip?dl=0
data/system/prebaked_results/montlake/weekday.bin,cd953900d32389809fb2dda206789528,https://www.dropbox.com/s/1aq7n9ow8tfqb5d/weekday.bin.zip?dl=0 data/system/prebaked_results/montlake/weekday.bin,cd953900d32389809fb2dda206789528,https://www.dropbox.com/s/1aq7n9ow8tfqb5d/weekday.bin.zip?dl=0

View File

@ -1,5 +1,6 @@
use crate::app::App; use crate::app::App;
use crate::info::{header_btns, make_tabs, Details, Tab}; use crate::info::{header_btns, make_tabs, Details, Tab};
use abstutil::prettyprint_usize;
use ezgui::{EventCtx, Line, TextExt, Widget}; use ezgui::{EventCtx, Line, TextExt, Widget};
use map_model::ParkingLotID; use map_model::ParkingLotID;
@ -8,16 +9,22 @@ pub fn info(ctx: &mut EventCtx, app: &App, details: &mut Details, id: ParkingLot
let pl = app.primary.map.get_pl(id); let pl = app.primary.map.get_pl(id);
if let Some(n) = pl.capacity { if let Some(n) = pl.capacity {
rows.push(format!("{} spots (from OSM)", n).draw_text(ctx)); rows.push(format!("{} spots (from OSM)", prettyprint_usize(n)).draw_text(ctx));
} }
// 250 square feet is around 23 square meters // 250 square feet is around 23 square meters
rows.push(format!("{} spots (from area)", (pl.polygon.area() / 23.0) as usize).draw_text(ctx)); rows.push(
format!(
"{} spots (from area)",
prettyprint_usize((pl.polygon.area() / 23.0) as usize)
)
.draw_text(ctx),
);
rows.push( rows.push(
format!( format!(
"{} spots (from geometry)", "{} spots (from geometry)",
app.primary.draw_map.get_pl(id).inferred_spots prettyprint_usize(pl.spots.len())
) )
.draw_text(ctx), .draw_text(ctx),
); );

View File

@ -10,7 +10,6 @@ use map_model::{
pub struct DrawParkingLot { pub struct DrawParkingLot {
pub id: ParkingLotID, pub id: ParkingLotID,
pub inferred_spots: usize,
draw: Drawable, draw: Drawable,
} }
@ -59,11 +58,33 @@ impl DrawParkingLot {
front_path_line.make_polygons(NORMAL_LANE_THICKNESS), front_path_line.make_polygons(NORMAL_LANE_THICKNESS),
); );
batch.push(cs.parking_lot, lot.polygon.clone()); batch.push(cs.parking_lot, lot.polygon.clone());
let inferred_spots = infer_spots(cs, lot, &mut batch); for aisle in &lot.aisles {
let aisle_thickness = NORMAL_LANE_THICKNESS / 2.0;
batch.push(
cs.driving_lane,
PolyLine::unchecked_new(aisle.clone()).make_polygons(aisle_thickness),
);
}
let width = NORMAL_LANE_THICKNESS;
let height = 0.8 * PARKING_SPOT_LENGTH;
for (pt, angle) in &lot.spots {
let left = pt.project_away(width / 2.0, angle.rotate_degs(90.0));
let right = pt.project_away(width / 2.0, angle.rotate_degs(-90.0));
batch.push(
cs.general_road_marking,
PolyLine::new(vec![
left.project_away(height, *angle),
left,
right,
right.project_away(height, *angle),
])
.make_polygons(Distance::meters(0.25)),
);
}
DrawParkingLot { DrawParkingLot {
id: lot.id, id: lot.id,
inferred_spots,
draw: prerender.upload(batch), draw: prerender.upload(batch),
} }
} }
@ -95,89 +116,3 @@ impl Renderable for DrawParkingLot {
map.get_pl(self.id).polygon.contains_pt(pt) map.get_pl(self.id).polygon.contains_pt(pt)
} }
} }
fn line_valid(lot: &ParkingLot, 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 lot.aisles.iter().any(|pts| {
PolyLine::unchecked_new(pts.clone())
.intersection(&line.to_polyline())
.is_some()
}) {
return false;
}
true
}
// Returns the number of spots
fn infer_spots(cs: &ColorScheme, lot: &ParkingLot, batch: &mut GeomBatch) -> usize {
let mut total_spots = 0;
let mut finalized_lines = Vec::new();
for aisle in &lot.aisles {
let aisle_thickness = NORMAL_LANE_THICKNESS / 2.0;
let pl = PolyLine::unchecked_new(aisle.clone());
batch.push(cs.driving_lane, pl.make_polygons(aisle_thickness));
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.dist_along(start);
start += NORMAL_LANE_THICKNESS;
let theta = angle.rotate_degs(rotate);
lines.push(Line::new(
pt.project_away(aisle_thickness / 2.0, theta),
// The full PARKING_SPOT_LENGTH used for on-street is looking too
// conservative for some manually audited cases in Seattle
pt.project_away(aisle_thickness / 2.0 + 0.8 * PARKING_SPOT_LENGTH, theta),
));
}
lines
};
for pair in lines.windows(2) {
let l1 = &pair[0];
let l2 = &pair[1];
let back = Line::new(l1.pt2(), l2.pt2());
if l1.intersection(&l2).is_none()
&& l1.angle().approx_eq(l2.angle(), 5.0)
&& line_valid(lot, l1, &finalized_lines)
&& line_valid(lot, l2, &finalized_lines)
&& line_valid(lot, &back, &finalized_lines)
{
total_spots += 1;
batch.push(
cs.general_road_marking,
l1.make_polygons(Distance::meters(0.25)),
);
batch.push(
cs.general_road_marking,
l2.make_polygons(Distance::meters(0.25)),
);
batch.push(
cs.general_road_marking,
back.make_polygons(Distance::meters(0.25)),
);
finalized_lines.push(l1.clone());
finalized_lines.push(l2.clone());
finalized_lines.push(back);
}
}
}
}
total_spots
}

View File

@ -8,25 +8,25 @@ pub struct Angle(f64);
impl Angle { impl Angle {
pub const ZERO: Angle = Angle(0.0); pub const ZERO: Angle = Angle(0.0);
pub(crate) fn new(rads: f64) -> Angle { pub(crate) fn new_rads(rads: f64) -> Angle {
// Retain more precision for angles... // Retain more precision for angles...
Angle((rads * 10_000_000.0).round() / 10_000_000.0) Angle((rads * 10_000_000.0).round() / 10_000_000.0)
} }
pub fn new_degs(degs: f64) -> Angle { pub fn new_degs(degs: f64) -> Angle {
Angle::new(degs.to_radians()) Angle::new_rads(degs.to_radians())
} }
pub fn opposite(self) -> Angle { pub fn opposite(self) -> Angle {
Angle::new(self.0 + std::f64::consts::PI) Angle::new_rads(self.0 + std::f64::consts::PI)
} }
pub fn invert_y(self) -> Angle { pub fn invert_y(self) -> Angle {
Angle::new(2.0 * std::f64::consts::PI - self.0) Angle::new_rads(2.0 * std::f64::consts::PI - self.0)
} }
pub fn rotate_degs(self, degrees: f64) -> Angle { pub fn rotate_degs(self, degrees: f64) -> Angle {
Angle::new(self.0 + degrees.to_radians()) Angle::new_rads(self.0 + degrees.to_radians())
} }
pub fn normalized_radians(self) -> f64 { pub fn normalized_radians(self) -> f64 {
@ -65,10 +65,29 @@ impl fmt::Display for Angle {
} }
} }
impl std::ops::Add for Angle {
type Output = Angle;
fn add(self, other: Angle) -> Angle {
Angle::new_rads(self.0 + other.0)
}
}
impl std::ops::Neg for Angle { impl std::ops::Neg for Angle {
type Output = Angle; type Output = Angle;
fn neg(self) -> Angle { fn neg(self) -> Angle {
Angle::new(-self.0) Angle::new_rads(-self.0)
}
}
impl std::ops::Div<f64> for Angle {
type Output = Angle;
fn div(self, scalar: f64) -> Angle {
if scalar == 0.0 {
panic!("Can't divide {} / {}", self, scalar);
}
Angle::new_rads(self.0 / scalar)
} }
} }

View File

@ -144,6 +144,10 @@ impl Line {
self.percent_along(dist / len) self.percent_along(dist / len)
} }
pub fn middle(&self) -> Pt2D {
self.dist_along(self.length() / 2.0)
}
pub fn unbounded_percent_along(&self, percent: f64) -> Pt2D { pub fn unbounded_percent_along(&self, percent: f64) -> Pt2D {
Pt2D::new( Pt2D::new(
self.pt1().x() + percent * (self.pt2().x() - self.pt1().x()), self.pt1().x() + percent * (self.pt2().x() - self.pt1().x()),

View File

@ -115,7 +115,7 @@ impl Pt2D {
pub fn angle_to(self, to: Pt2D) -> Angle { pub fn angle_to(self, to: Pt2D) -> Angle {
// DON'T invert y here // DON'T invert y here
Angle::new((to.y() - self.y()).atan2(to.x() - self.x())) Angle::new_rads((to.y() - self.y()).atan2(to.x() - self.x()))
} }
pub fn offset(self, dx: f64, dy: f64) -> Pt2D { pub fn offset(self, dx: f64, dy: f64) -> Pt2D {

View File

@ -2,10 +2,10 @@ use crate::make::sidewalk_finder::find_sidewalk_points;
use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot}; use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot};
use crate::{ use crate::{
osm, Building, BuildingID, FrontPath, LaneID, LaneType, Map, OffstreetParking, ParkingLot, osm, Building, BuildingID, FrontPath, LaneID, LaneType, Map, OffstreetParking, ParkingLot,
ParkingLotID, Position, ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_SPOT_LENGTH,
}; };
use abstutil::Timer; use abstutil::Timer;
use geom::{Distance, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring}; use geom::{Angle, Distance, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring};
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
pub fn make_all_buildings( pub fn make_all_buildings(
@ -183,6 +183,7 @@ pub fn make_all_parking_lots(
// TODO Rethink this approach. 250 square feet is around 23 square meters // TODO Rethink this approach. 250 square feet is around 23 square meters
capacity: orig.capacity, capacity: orig.capacity,
osm_id: orig.osm_id, osm_id: orig.osm_id,
spots: Vec::new(),
driveway_line, driveway_line,
driving_pos, driving_pos,
@ -226,6 +227,12 @@ pub fn make_all_parking_lots(
} }
} }
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"); timer.stop("convert parking lots");
results results
@ -252,3 +259,81 @@ fn get_address(tags: &BTreeMap<String, String>, sidewalk: LaneID, map: &Map) ->
_ => format!("??? {}", map.get_parent(sidewalk).get_name()), _ => format!("??? {}", map.get_parent(sidewalk).get_name()),
} }
} }
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.dist_along(start);
start += NORMAL_LANE_THICKNESS;
let theta = angle.rotate_degs(rotate);
lines.push(Line::new(
pt.project_away(aisle_thickness / 2.0, theta),
// The full PARKING_SPOT_LENGTH used for on-street is looking too
// conservative for some manually audited cases in Seattle
pt.project_away(aisle_thickness / 2.0 + 0.8 * PARKING_SPOT_LENGTH, theta),
));
}
lines
};
for pair in lines.windows(2) {
let l1 = &pair[0];
let l2 = &pair[1];
let back = Line::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(), 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
}

View File

@ -1,5 +1,5 @@
use crate::Position; use crate::Position;
use geom::{Line, PolyLine, Polygon, Pt2D}; use geom::{Angle, Line, PolyLine, Polygon, Pt2D};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
@ -22,6 +22,8 @@ pub struct ParkingLot {
pub aisles: Vec<Vec<Pt2D>>, pub aisles: Vec<Vec<Pt2D>>,
pub capacity: Option<usize>, pub capacity: Option<usize>,
pub osm_id: i64, pub osm_id: i64,
// The middle of the "T", pointing towards the parking aisle
pub spots: Vec<(Pt2D, Angle)>,
// Goes from the lot to the driving lane // Goes from the lot to the driving lane
pub driveway_line: PolyLine, pub driveway_line: PolyLine,