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