From ea6a5c0f9de07a42fd3afcce7e4533cb18e129a3 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 3 Jun 2020 11:50:46 -0700 Subject: [PATCH] encode parking spots cheaply, compute up-front --- data/MANIFEST.txt | 16 ++--- game/src/info/parking_lot.rs | 13 +++- game/src/render/parking_lot.rs | 113 +++++++------------------------- geom/src/angle.rs | 31 +++++++-- geom/src/line.rs | 4 ++ geom/src/pt.rs | 2 +- map_model/src/make/buildings.rs | 89 ++++++++++++++++++++++++- map_model/src/parking_lot.rs | 4 +- 8 files changed, 162 insertions(+), 110 deletions(-) diff --git a/data/MANIFEST.txt b/data/MANIFEST.txt index ed93056033..4c09a744bb 100644 --- a/data/MANIFEST.txt +++ b/data/MANIFEST.txt @@ -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/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/maps/ballard.bin,2abeb9837c0c2ecb89dfe7ec6c9223df,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/ballard.bin,78198be4e5a03a59c63cdd61846efc60,https://www.dropbox.com/s/u4rvz50she3yrk0/ballard.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/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/lakeslice.bin,6e02eaf499068e1a135a88fa38f8ae54,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/mt_baker.bin,57a2761b4d1dfe9ab23c5ab9ba7e70b8,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/west_seattle.bin,c9c394a9fb831bcbaa915bce81ae937f,https://www.dropbox.com/s/5pp1ik9l40yj3wh/west_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,061a9c3e377bac0ae5db460c67c3eea9,https://www.dropbox.com/s/99zi0gcbyvqrkud/lakeslice.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,09dd0d4220e1ebbed4fc730ed2081933,https://www.dropbox.com/s/cetje663p04cbgp/mt_baker.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,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/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 diff --git a/game/src/info/parking_lot.rs b/game/src/info/parking_lot.rs index c89fe11690..df9a0231e2 100644 --- a/game/src/info/parking_lot.rs +++ b/game/src/info/parking_lot.rs @@ -1,5 +1,6 @@ use crate::app::App; use crate::info::{header_btns, make_tabs, Details, Tab}; +use abstutil::prettyprint_usize; use ezgui::{EventCtx, Line, TextExt, Widget}; 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); 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 - 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( format!( "{} spots (from geometry)", - app.primary.draw_map.get_pl(id).inferred_spots + prettyprint_usize(pl.spots.len()) ) .draw_text(ctx), ); diff --git a/game/src/render/parking_lot.rs b/game/src/render/parking_lot.rs index 2cb07c4189..2ff4103aed 100644 --- a/game/src/render/parking_lot.rs +++ b/game/src/render/parking_lot.rs @@ -10,7 +10,6 @@ use map_model::{ pub struct DrawParkingLot { pub id: ParkingLotID, - pub inferred_spots: usize, draw: Drawable, } @@ -59,11 +58,33 @@ impl DrawParkingLot { front_path_line.make_polygons(NORMAL_LANE_THICKNESS), ); 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 { id: lot.id, - inferred_spots, draw: prerender.upload(batch), } } @@ -95,89 +116,3 @@ impl Renderable for DrawParkingLot { map.get_pl(self.id).polygon.contains_pt(pt) } } - -fn line_valid(lot: &ParkingLot, line: &Line, finalized_lines: &Vec) -> 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 -} diff --git a/geom/src/angle.rs b/geom/src/angle.rs index a39e783285..9b2809985b 100644 --- a/geom/src/angle.rs +++ b/geom/src/angle.rs @@ -8,25 +8,25 @@ pub struct Angle(f64); impl Angle { 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... Angle((rads * 10_000_000.0).round() / 10_000_000.0) } pub fn new_degs(degs: f64) -> Angle { - Angle::new(degs.to_radians()) + Angle::new_rads(degs.to_radians()) } 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 { - 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 { - Angle::new(self.0 + degrees.to_radians()) + Angle::new_rads(self.0 + degrees.to_radians()) } 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 { type Output = Angle; fn neg(self) -> Angle { - Angle::new(-self.0) + Angle::new_rads(-self.0) + } +} + +impl std::ops::Div 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) } } diff --git a/geom/src/line.rs b/geom/src/line.rs index a7c69a723f..fde23b2811 100644 --- a/geom/src/line.rs +++ b/geom/src/line.rs @@ -144,6 +144,10 @@ impl Line { 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 { Pt2D::new( self.pt1().x() + percent * (self.pt2().x() - self.pt1().x()), diff --git a/geom/src/pt.rs b/geom/src/pt.rs index b739c1580a..2750687254 100644 --- a/geom/src/pt.rs +++ b/geom/src/pt.rs @@ -115,7 +115,7 @@ impl Pt2D { pub fn angle_to(self, to: Pt2D) -> Angle { // 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 { diff --git a/map_model/src/make/buildings.rs b/map_model/src/make/buildings.rs index 5f59e654ec..9dc5e2c076 100644 --- a/map_model/src/make/buildings.rs +++ b/map_model/src/make/buildings.rs @@ -2,10 +2,10 @@ use crate::make::sidewalk_finder::find_sidewalk_points; use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot}; use crate::{ osm, Building, BuildingID, FrontPath, LaneID, LaneType, Map, OffstreetParking, ParkingLot, - ParkingLotID, Position, + ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_SPOT_LENGTH, }; 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}; 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 capacity: orig.capacity, osm_id: orig.osm_id, + spots: Vec::new(), driveway_line, 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"); results @@ -252,3 +259,81 @@ fn get_address(tags: &BTreeMap, sidewalk: LaneID, map: &Map) -> _ => format!("??? {}", map.get_parent(sidewalk).get_name()), } } + +fn infer_spots(lot_polygon: &Polygon, aisles: &Vec>) -> 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>, + line: &Line, + finalized_lines: &Vec, +) -> 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 +} diff --git a/map_model/src/parking_lot.rs b/map_model/src/parking_lot.rs index 73323a5db8..88bc2a2d9e 100644 --- a/map_model/src/parking_lot.rs +++ b/map_model/src/parking_lot.rs @@ -1,5 +1,5 @@ use crate::Position; -use geom::{Line, PolyLine, Polygon, Pt2D}; +use geom::{Angle, Line, PolyLine, Polygon, Pt2D}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -22,6 +22,8 @@ pub struct ParkingLot { pub aisles: Vec>, pub capacity: Option, 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 pub driveway_line: PolyLine,