1 use crate::make::match_points_to_lanes;
2 use crate::raw::RawParkingLot;
3 use crate::{
4 osm, Map, ParkingLot, ParkingLotID, PathConstraints, Position, NORMAL_LANE_THICKNESS,
5 PARKING_LOT_SPOT_LENGTH,
6 };
7 use abstutil::Timer;
8 use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring};
9 use std::collections::HashSet;
10
make_all_parking_lots( input: &Vec<RawParkingLot>, aisles: &Vec<(osm::WayID, Vec<Pt2D>)>, map: &Map, timer: &mut Timer, ) -> Vec<ParkingLot>11 pub fn make_all_parking_lots(
12 input: &Vec<RawParkingLot>,
13 aisles: &Vec<(osm::WayID, Vec<Pt2D>)>,
14 map: &Map,
15 timer: &mut Timer,
16 ) -> Vec<ParkingLot> {
17 timer.start("convert parking lots");
18 let mut center_per_lot: Vec<HashablePt2D> = Vec::new();
19 let mut query: HashSet<HashablePt2D> = HashSet::new();
20 for lot in input {
21 let center = lot.polygon.center().to_hashable();
22 center_per_lot.push(center);
23 query.insert(center);
24 }
25
26 let sidewalk_buffer = Distance::meters(7.5);
27 let driveway_buffer = Distance::meters(7.0);
28 let sidewalk_pts = match_points_to_lanes(
29 map.get_bounds(),
30 query,
31 map.all_lanes(),
32 |l| l.is_walkable(),
33 sidewalk_buffer,
34 Distance::meters(1000.0),
35 timer,
36 );
37
38 let mut results = Vec::new();
39 timer.start_iter("create parking lot driveways", center_per_lot.len());
40 for (lot_center, orig) in center_per_lot.into_iter().zip(input.iter()) {
41 timer.next();
42 // TODO Refactor this
43 if let Some(sidewalk_pos) = sidewalk_pts.get(&lot_center) {
44 let sidewalk_line = match Line::new(lot_center.to_pt2d(), sidewalk_pos.pt(map)) {
45 Some(l) => trim_path(&orig.polygon, l),
46 None => {
47 timer.warn(format!(
48 "Skipping parking lot {} because front path has 0 length",
49 orig.osm_id
50 ));
51 continue;
52 }
53 };
54
55 // Can this lot have a driveway? If it's not next to a driving lane, then no.
56 let mut driveway: Option<(PolyLine, Position)> = None;
57 let sidewalk_lane = sidewalk_pos.lane();
58 if let Some(driving_pos) = map
59 .get_parent(sidewalk_lane)
60 .find_closest_lane(sidewalk_lane, |l| PathConstraints::Car.can_use(l, map), map)
61 .and_then(|l| {
62 sidewalk_pos
63 .equiv_pos(l, map)
64 .buffer_dist(driveway_buffer, map)
65 })
66 {
67 driveway = Some((
68 PolyLine::must_new(vec![
69 sidewalk_line.pt1(),
70 sidewalk_line.pt2(),
71 driving_pos.pt(map),
72 ]),
73 driving_pos,
74 ));
75 }
76 if let Some((driveway_line, driving_pos)) = driveway {
77 let id = ParkingLotID(results.len());
78 results.push(ParkingLot {
79 id,
80 polygon: orig.polygon.clone(),
81 aisles: Vec::new(),
82 osm_id: orig.osm_id,
83 spots: Vec::new(),
84 extra_spots: 0,
85
86 driveway_line,
87 driving_pos,
88 sidewalk_line,
89 sidewalk_pos: *sidewalk_pos,
90 });
91 } else {
92 timer.warn(format!(
93 "Parking lot from {}, near sidewalk {}, can't have a driveway.",
94 orig.osm_id,
95 sidewalk_pos.lane()
96 ));
97 }
98 }
99 }
100 timer.note(format!(
101 "Discarded {} parking lots that weren't close enough to a sidewalk",
102 input.len() - results.len()
103 ));
104
105 let mut closest: FindClosest<ParkingLotID> = FindClosest::new(map.get_bounds());
106 for lot in &results {
107 closest.add(lot.id, lot.polygon.points());
108 }
109 timer.start_iter("match parking aisles", aisles.len());
110 for (aisle_id, pts) in aisles {
111 timer.next();
112 // Use the center of all the aisle points to match it to a lot
113 let candidates: Vec<ParkingLotID> = closest
114 .all_close_pts(Pt2D::center(&pts), Distance::meters(500.0))
115 .into_iter()
116 .map(|(id, _, _)| id)
117 .collect();
118
119 match Ring::split_points(pts) {
120 Ok((polylines, rings)) => {
121 'PL: for pl in polylines {
122 for id in &candidates {
123 let lot = &mut results[id.0];
124 if let Some(segment) = lot.polygon.clip_polyline(&pl) {
125 lot.aisles.push(segment);
126 continue 'PL;
127 }
128 }
129 }
130 'RING: for ring in rings {
131 for id in &candidates {
132 let lot = &mut results[id.0];
133 if let Some(segment) = lot.polygon.clip_ring(&ring) {
134 lot.aisles.push(segment);
135 continue 'RING;
136 }
137 }
138 }
139 }
140 Err(err) => {
141 timer.warn(format!(
142 "Parking aisle {} has weird geometry: {}",
143 aisle_id, err
144 ));
145 }
146 }
147 }
148
149 timer.start_iter("generate parking lot spots", results.len());
150 for lot in results.iter_mut() {
151 timer.next();
152 lot.spots = infer_spots(&lot.polygon, &lot.aisles);
153
154 // Guess how many extra spots are available, that maybe aren't renderable.
155 if lot.spots.is_empty() {
156 // No parking aisles. Just guess based on the area. One spot per 30m^2 is a quick guess
157 // from looking at examples with aisles.
158 lot.extra_spots = (lot.polygon.area() / 30.0) as usize;
159 }
160 }
161
162 timer.stop("convert parking lots");
163
164 results
165 }
166
167 // Adjust the path to start on the building's border, not center
trim_path(poly: &Polygon, path: Line) -> Line168 fn trim_path(poly: &Polygon, path: Line) -> Line {
169 for bldg_line in poly.points().windows(2) {
170 if let Some(l1) = Line::new(bldg_line[0], bldg_line[1]) {
171 if let Some(hit) = l1.intersection(&path) {
172 if let Some(l2) = Line::new(hit, path.pt2()) {
173 return l2;
174 }
175 }
176 }
177 }
178 // Just give up
179 path
180 }
181
infer_spots(lot_polygon: &Polygon, aisles: &Vec<Vec<Pt2D>>) -> Vec<(Pt2D, Angle)>182 fn infer_spots(lot_polygon: &Polygon, aisles: &Vec<Vec<Pt2D>>) -> Vec<(Pt2D, Angle)> {
183 let mut spots = Vec::new();
184 let mut finalized_lines = Vec::new();
185
186 for aisle in aisles {
187 let aisle_thickness = NORMAL_LANE_THICKNESS / 2.0;
188 let pl = PolyLine::unchecked_new(aisle.clone());
189
190 for rotate in vec![90.0, -90.0] {
191 // Blindly generate all of the lines
192 let lines = {
193 let mut lines = Vec::new();
194 let mut start = Distance::ZERO;
195 while start + NORMAL_LANE_THICKNESS < pl.length() {
196 let (pt, angle) = pl.must_dist_along(start);
197 start += NORMAL_LANE_THICKNESS;
198 let theta = angle.rotate_degs(rotate);
199 lines.push(Line::must_new(
200 pt.project_away(aisle_thickness / 2.0, theta),
201 pt.project_away(aisle_thickness / 2.0 + PARKING_LOT_SPOT_LENGTH, theta),
202 ));
203 }
204 lines
205 };
206
207 for pair in lines.windows(2) {
208 let l1 = &pair[0];
209 let l2 = &pair[1];
210 if let Some(back) = Line::new(l1.pt2(), l2.pt2()) {
211 if l1.intersection(&l2).is_none()
212 && l1.angle().approx_eq(l2.angle(), 5.0)
213 && line_valid(lot_polygon, aisles, l1, &finalized_lines)
214 && line_valid(lot_polygon, aisles, l2, &finalized_lines)
215 && line_valid(lot_polygon, aisles, &back, &finalized_lines)
216 {
217 let avg_angle = (l1.angle() + l2.angle()) / 2.0;
218 spots.push((back.middle().unwrap(), avg_angle.opposite()));
219 finalized_lines.push(l1.clone());
220 finalized_lines.push(l2.clone());
221 finalized_lines.push(back);
222 }
223 }
224 }
225 }
226 }
227 spots
228 }
229
line_valid( lot_polygon: &Polygon, aisles: &Vec<Vec<Pt2D>>, line: &Line, finalized_lines: &Vec<Line>, ) -> bool230 fn line_valid(
231 lot_polygon: &Polygon,
232 aisles: &Vec<Vec<Pt2D>>,
233 line: &Line,
234 finalized_lines: &Vec<Line>,
235 ) -> bool {
236 // Don't leak out of the parking lot
237 // TODO Entire line
238 if !lot_polygon.contains_pt(line.pt1()) || !lot_polygon.contains_pt(line.pt2()) {
239 return false;
240 }
241
242 // Don't let this line hit another line
243 if finalized_lines.iter().any(|other| line.crosses(other)) {
244 return false;
245 }
246
247 // Don't hit an aisle
248 if aisles.iter().any(|pts| {
249 PolyLine::unchecked_new(pts.clone())
250 .intersection(&line.to_polyline())
251 .is_some()
252 }) {
253 return false;
254 }
255
256 true
257 }
258