convert_osm/
parking.rs
1use abstutil::{Tags, Timer};
2use geom::{Distance, FindClosest, PolyLine};
3use kml::ExtraShapes;
4use osm2streets::{osm, RoadID};
5use raw_map::RawMap;
6
7use crate::{OnstreetParking, Options, PrivateOffstreetParking, PublicOffstreetParking};
8
9const DIRECTED_ROAD_THICKNESS: Distance = Distance::const_meters(2.5);
11
12pub fn apply_parking(map: &mut RawMap, opts: &Options, timer: &mut Timer) {
13 match opts.onstreet_parking {
14 OnstreetParking::JustOSM => {}
15 OnstreetParking::Blockface(ref path) => {
16 use_parking_hints(map, path.clone(), timer);
17 }
18 }
19 match opts.public_offstreet_parking {
20 PublicOffstreetParking::None => {}
21 PublicOffstreetParking::Gis(ref path) => {
22 use_offstreet_parking(map, path.clone(), timer);
23 }
24 }
25 apply_private_offstreet_parking(map, &opts.private_offstreet_parking);
26}
27
28fn unknown_parking(tags: &Tags) -> bool {
29 !tags.contains_key("parking:lane:left")
30 && !tags.contains_key("parking:lane:right")
31 && !tags.contains_key("parking:lane:both")
32 && !tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link", "service"])
33 && !tags.is("junction", "roundabout")
34}
35
36fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
37 timer.start("apply parking hints");
38 let shapes: ExtraShapes = abstio::read_binary(path, timer);
39
40 let mut closest: FindClosest<(RoadID, bool)> = FindClosest::new();
42 for (id, r) in &map.streets.roads {
43 if r.is_service() || !r.is_driveable() {
44 continue;
45 }
46 closest.add(
47 (*id, true),
48 r.reference_line
49 .must_shift_right(DIRECTED_ROAD_THICKNESS)
50 .points(),
51 );
52 closest.add(
53 (*id, false),
54 r.reference_line
55 .must_shift_left(DIRECTED_ROAD_THICKNESS)
56 .points(),
57 );
58 }
59
60 for s in shapes.shapes.into_iter() {
61 let pts = map.streets.gps_bounds.convert(&s.points);
62 if pts.len() <= 1 {
63 continue;
64 }
65 let middle = if let Ok(pl) = PolyLine::new(pts) {
70 pl.middle()
71 } else {
72 continue;
74 };
75 if let Some(((r, fwds), _)) = closest.closest_pt(middle, DIRECTED_ROAD_THICKNESS * 5.0) {
76 let mut tags = map.road_to_osm_tags(r).cloned().unwrap_or_else(Tags::empty);
77
78 if !unknown_parking(&tags) {
80 continue;
81 }
82
83 let category = s.attributes.get("PARKING_CATEGORY");
84 let has_parking = category != Some(&"None".to_string())
85 && category != Some(&"No Parking Allowed".to_string());
86
87 let definitely_no_parking =
88 tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link", "trunk"]);
89 if has_parking && definitely_no_parking {
90 warn!(
91 "Blockface says there's parking along motorway {}, ignoring",
92 r
93 );
94 continue;
95 }
96
97 if !fwds && tags.is("dual_carriageway", "yes") {
99 continue;
100 }
101 if tags.is("junction", "intersection") {
103 continue;
104 }
105
106 if let Some(both) = tags.remove("parking:lane:both") {
107 tags.insert("parking:lane:left", both.clone());
108 tags.insert("parking:lane:right", both);
109 }
110
111 tags.insert(
112 if fwds {
113 "parking:lane:right"
114 } else {
115 "parking:lane:left"
116 },
117 if has_parking {
118 "parallel"
119 } else {
120 "no_parking"
121 },
122 );
123
124 if tags.contains_key("parking:lane:left")
126 && tags.get("parking:lane:left") == tags.get("parking:lane:right")
127 {
128 let value = tags.remove("parking:lane:left").unwrap();
129 tags.remove("parking:lane:right").unwrap();
130 tags.insert("parking:lane:both", value);
131 }
132 let lane_specs_ltr = osm2streets::get_lane_specs_ltr(&tags, &map.streets.config);
137 let road = map.streets.roads.get_mut(&r).unwrap();
138 road.lane_specs_ltr = lane_specs_ltr;
139 road.update_center_line(map.streets.config.driving_side);
140 let i1 = road.src_i;
141 let i2 = road.dst_i;
142 map.streets.update_i(i1);
143 map.streets.update_i(i2);
144 }
145 }
146 timer.stop("apply parking hints");
147}
148
149fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
150 timer.start("match offstreet parking points");
151 let shapes: ExtraShapes = abstio::read_binary(path, timer);
152
153 let mut closest: FindClosest<osm::OsmID> = FindClosest::new();
154 for (id, b) in &map.buildings {
155 closest.add_polygon(*id, &b.polygon);
156 }
157
158 let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
160 assert_eq!(s.points.len(), 1);
161 let pt = s.points[0].to_pt(&map.streets.gps_bounds);
162 let (id, _) = closest.closest_pt(pt, Distance::meters(50.0))?;
163 if !map.buildings[&id].polygon.contains_pt(pt) {
165 return None;
166 }
167 let name = s.attributes.get("DEA_FACILITY_NAME")?.to_string();
168 let num_stalls = s.attributes.get("DEA_STALLS")?.parse::<usize>().ok()?;
169 if num_stalls == 0 {
171 return None;
172 }
173
174 let bldg = map.buildings.get_mut(&id).unwrap();
175 if bldg.num_parking_spots > 0 {
176 let old_name = bldg.public_garage_name.take().unwrap();
178 println!(
179 "Two offstreet parking hints apply to {}: {} @ {}, and {} @ {}",
180 id, bldg.num_parking_spots, old_name, num_stalls, name
181 );
182 bldg.public_garage_name = Some(format!("{} and {}", old_name, name));
183 bldg.num_parking_spots += num_stalls;
184 } else {
185 bldg.public_garage_name = Some(name);
186 bldg.num_parking_spots = num_stalls;
187 }
188 None
189 });
190
191 for s in shapes.shapes.into_iter() {
192 handle_shape(s);
193 }
194 timer.stop("match offstreet parking points");
195}
196
197fn apply_private_offstreet_parking(map: &mut RawMap, policy: &PrivateOffstreetParking) {
198 match policy {
199 PrivateOffstreetParking::FixedPerBldg(n) => {
200 for b in map.buildings.values_mut() {
201 if b.public_garage_name.is_none() {
202 assert_eq!(b.num_parking_spots, 0);
203
204 if b.osm_tags.is("building", "parking") || b.osm_tags.is("amenity", "parking") {
206 let levels = b
207 .osm_tags
208 .get("parking:levels")
209 .or_else(|| b.osm_tags.get("building:levels"))
210 .and_then(|x| x.parse::<usize>().ok())
211 .unwrap_or(1);
212 b.num_parking_spots = ((b.polygon.area() / 30.0) as usize) * levels;
215 b.amenities.retain(|a| a.amenity_type != "parking");
217 } else {
218 b.num_parking_spots = *n;
219 }
220 }
221 }
222 }
223 }
224}