mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 23:15:24 +03:00
encode parking spots cheaply, compute up-front
This commit is contained in:
parent
b69d155812
commit
ea6a5c0f9d
@ -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
|
||||
|
@ -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),
|
||||
);
|
||||
|
@ -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<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
|
||||
}
|
||||
|
@ -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<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)
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
|
@ -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 {
|
||||
|
@ -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<String, String>, sidewalk: LaneID, map: &Map) ->
|
||||
_ => 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
|
||||
}
|
||||
|
@ -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<Vec<Pt2D>>,
|
||||
pub capacity: Option<usize>,
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user