1use std::collections::{BTreeMap, HashSet};
2
3use rand::{Rng, SeedableRng};
4use rand_xorshift::XorShiftRng;
5
6use abstutil::{Tags, Timer};
7use geom::{Distance, HashablePt2D, Line};
8use raw_map::RawBuilding;
9
10use crate::make::{match_points_to_lanes, trim_path};
11use crate::{
12 osm, Amenity, Building, BuildingID, BuildingType, LaneID, Map, NamePerLanguage,
13 OffstreetParking,
14};
15
16pub fn make_all_buildings(
18 input: &BTreeMap<osm::OsmID, RawBuilding>,
19 map: &Map,
20 keep_bldg_tags: bool,
21 timer: &mut Timer,
22) -> Vec<Building> {
23 timer.start("convert buildings");
24 let mut center_per_bldg: BTreeMap<osm::OsmID, HashablePt2D> = BTreeMap::new();
25 let mut query: HashSet<HashablePt2D> = HashSet::new();
26 timer.start_iter("get building center points", input.len());
27 for (id, b) in input {
28 timer.next();
29 let center = b.polygon.center().to_hashable();
30 center_per_bldg.insert(*id, center);
31 query.insert(center);
32 }
33
34 let sidewalk_buffer = Distance::meters(7.5);
35 let sidewalk_pts = match_points_to_lanes(
36 map,
37 query,
38 |l| l.is_walkable(),
39 sidewalk_buffer,
41 Distance::meters(1000.0),
43 timer,
44 );
45
46 let mut results = Vec::new();
47 timer.start_iter("match buildings to sidewalks", center_per_bldg.len());
48 for (orig_id, bldg_center) in center_per_bldg {
49 timer.next();
50 if let Some(sidewalk_pos) = sidewalk_pts.get(&bldg_center) {
51 let b = &input[&orig_id];
52 let sidewalk_line = match Line::new(bldg_center.to_pt2d(), sidewalk_pos.pt(map)) {
53 Ok(l) => trim_path(&b.polygon, l),
54 Err(_) => {
55 warn!(
56 "Skipping building {} because front path has 0 length",
57 orig_id
58 );
59 continue;
60 }
61 };
62
63 let id = BuildingID(results.len());
64
65 let mut rng = XorShiftRng::seed_from_u64(orig_id.inner_id() as u64);
66 let levels = b
68 .osm_tags
69 .get("building:levels")
70 .and_then(|x| x.parse::<f64>().ok())
71 .unwrap_or(1.0);
72
73 results.push(Building {
74 id,
75 polygon: b.polygon.clone(),
76 levels,
77 address: get_address(&b.osm_tags, sidewalk_pos.lane(), map),
78 name: NamePerLanguage::new(&b.osm_tags),
79 orig_id,
80 label_center: b.polygon.polylabel(),
81 amenities: if keep_bldg_tags {
82 b.amenities.clone()
83 } else {
84 b.amenities
85 .iter()
86 .map(|a| {
87 let mut a = a.clone();
88 a.osm_tags = Tags::empty();
89 a
90 })
91 .collect()
92 },
93 bldg_type: classify_bldg(
94 &b.osm_tags,
95 &b.amenities,
96 levels,
97 b.polygon.area(),
98 &mut rng,
99 ),
100 parking: if let Some(n) = b.public_garage_name.clone() {
101 OffstreetParking::PublicGarage(n, b.num_parking_spots)
102 } else {
103 OffstreetParking::Private(
104 b.num_parking_spots,
105 b.osm_tags.is("building", "parking") || b.osm_tags.is("amenity", "parking"),
106 )
107 },
108 osm_tags: if keep_bldg_tags {
109 b.osm_tags.clone()
110 } else {
111 Tags::empty()
112 },
113
114 sidewalk_pos: *sidewalk_pos,
115 driveway_geom: sidewalk_line.to_polyline(),
116 });
117 }
118 }
119
120 info!(
121 "Discarded {} buildings that weren't close enough to a sidewalk",
122 input.len() - results.len()
123 );
124 timer.stop("convert buildings");
125
126 results
127}
128
129fn get_address(tags: &Tags, sidewalk: LaneID, map: &Map) -> String {
132 let street = tags
133 .get("addr:street")
134 .cloned()
135 .unwrap_or_else(|| map.get_parent(sidewalk).get_name(None));
136 match tags.get("addr:housenumber") {
137 Some(num) => format!("{} {}", num, street),
138 None => street,
139 }
140}
141
142fn classify_bldg(
143 tags: &Tags,
144 amenities: &[Amenity],
145 levels: f64,
146 ground_area_sq_meters: f64,
147 rng: &mut XorShiftRng,
148) -> BuildingType {
149 let mut commercial = false;
152
153 let area_sq_meters = levels * ground_area_sq_meters;
154
155 if !amenities.is_empty() {
160 commercial = true;
161 }
162
163 if tags.is("ruins", "yes") {
164 if commercial {
165 return BuildingType::Commercial(0);
166 }
167 return BuildingType::Empty;
168 }
169
170 let mut residents: usize = 0;
171 let mut workers: usize = 0;
172
173 #[allow(clippy::if_same_then_else)] if tags.is_any(
175 "building",
176 vec![
177 "office",
178 "industrial",
179 "commercial",
180 "retail",
181 "warehouse",
182 "civic",
183 "public",
184 ],
185 ) {
186 workers = (area_sq_meters / 10.0) as usize;
191 } else if tags.is_any(
192 "building",
193 vec!["school", "university", "construction", "church"],
194 ) {
195 return BuildingType::Empty;
197 } else if tags.is_any(
198 "building",
199 vec![
200 "garage",
201 "garages",
202 "shed",
203 "roof",
204 "greenhouse",
205 "farm_auxiliary",
206 "barn",
207 "service",
208 ],
209 ) {
210 return BuildingType::Empty;
211 } else if tags.is_any(
212 "building",
213 vec!["house", "detached", "semidetached_house", "farm"],
214 ) {
215 residents = rng.gen_range(0..3);
216 } else if tags.is_any("building", vec!["hut", "static_caravan", "cabin"]) {
217 residents = rng.gen_range(0..2);
218 } else if tags.is_any("building", vec!["apartments", "terrace", "residential"]) {
219 residents = (area_sq_meters / 10.0) as usize;
224 } else {
225 residents = rng.gen_range(0..2);
226 }
227
228 if commercial && workers == 0 {
229 workers = (residents as f64 / 3.0) as usize;
231 }
232
233 if commercial {
234 if residents > 0 {
235 return BuildingType::ResidentialCommercial(residents, workers);
236 }
237 return BuildingType::Commercial(workers);
238 }
239 BuildingType::Residential {
240 num_residents: residents,
241 num_housing_units: 1,
242 }
243}