use std::collections::{BTreeMap, HashSet};
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use abstutil::{Tags, Timer};
use geom::{Distance, HashablePt2D, Line};
use raw_map::RawBuilding;
use crate::make::{match_points_to_lanes, trim_path};
use crate::{
osm, Amenity, Building, BuildingID, BuildingType, LaneID, Map, NamePerLanguage,
OffstreetParking,
};
pub fn make_all_buildings(
input: &BTreeMap<osm::OsmID, RawBuilding>,
map: &Map,
keep_bldg_tags: bool,
timer: &mut Timer,
) -> Vec<Building> {
timer.start("convert buildings");
let mut center_per_bldg: BTreeMap<osm::OsmID, HashablePt2D> = BTreeMap::new();
let mut query: HashSet<HashablePt2D> = HashSet::new();
timer.start_iter("get building center points", input.len());
for (id, b) in input {
timer.next();
let center = b.polygon.center().to_hashable();
center_per_bldg.insert(*id, center);
query.insert(center);
}
let sidewalk_buffer = Distance::meters(7.5);
let sidewalk_pts = match_points_to_lanes(
map,
query,
|l| l.is_walkable(),
sidewalk_buffer,
Distance::meters(1000.0),
timer,
);
let mut results = Vec::new();
timer.start_iter("match buildings to sidewalks", center_per_bldg.len());
for (orig_id, bldg_center) in center_per_bldg {
timer.next();
if let Some(sidewalk_pos) = sidewalk_pts.get(&bldg_center) {
let b = &input[&orig_id];
let sidewalk_line = match Line::new(bldg_center.to_pt2d(), sidewalk_pos.pt(map)) {
Ok(l) => trim_path(&b.polygon, l),
Err(_) => {
warn!(
"Skipping building {} because front path has 0 length",
orig_id
);
continue;
}
};
let id = BuildingID(results.len());
let mut rng = XorShiftRng::seed_from_u64(orig_id.inner_id() as u64);
let levels = b
.osm_tags
.get("building:levels")
.and_then(|x| x.parse::<f64>().ok())
.unwrap_or(1.0);
results.push(Building {
id,
polygon: b.polygon.clone(),
levels,
address: get_address(&b.osm_tags, sidewalk_pos.lane(), map),
name: NamePerLanguage::new(&b.osm_tags),
orig_id,
label_center: b.polygon.polylabel(),
amenities: if keep_bldg_tags {
b.amenities.clone()
} else {
b.amenities
.iter()
.map(|a| {
let mut a = a.clone();
a.osm_tags = Tags::empty();
a
})
.collect()
},
bldg_type: classify_bldg(
&b.osm_tags,
&b.amenities,
levels,
b.polygon.area(),
&mut rng,
),
parking: if let Some(n) = b.public_garage_name.clone() {
OffstreetParking::PublicGarage(n, b.num_parking_spots)
} else {
OffstreetParking::Private(
b.num_parking_spots,
b.osm_tags.is("building", "parking") || b.osm_tags.is("amenity", "parking"),
)
},
osm_tags: if keep_bldg_tags {
b.osm_tags.clone()
} else {
Tags::empty()
},
sidewalk_pos: *sidewalk_pos,
driveway_geom: sidewalk_line.to_polyline(),
});
}
}
info!(
"Discarded {} buildings that weren't close enough to a sidewalk",
input.len() - results.len()
);
timer.stop("convert buildings");
results
}
fn get_address(tags: &Tags, sidewalk: LaneID, map: &Map) -> String {
let street = tags
.get("addr:street")
.cloned()
.unwrap_or_else(|| map.get_parent(sidewalk).get_name(None));
match tags.get("addr:housenumber") {
Some(num) => format!("{} {}", num, street),
None => street,
}
}
fn classify_bldg(
tags: &Tags,
amenities: &[Amenity],
levels: f64,
ground_area_sq_meters: f64,
rng: &mut XorShiftRng,
) -> BuildingType {
let mut commercial = false;
let area_sq_meters = levels * ground_area_sq_meters;
if !amenities.is_empty() {
commercial = true;
}
if tags.is("ruins", "yes") {
if commercial {
return BuildingType::Commercial(0);
}
return BuildingType::Empty;
}
let mut residents: usize = 0;
let mut workers: usize = 0;
#[allow(clippy::if_same_then_else)] if tags.is_any(
"building",
vec![
"office",
"industrial",
"commercial",
"retail",
"warehouse",
"civic",
"public",
],
) {
workers = (area_sq_meters / 10.0) as usize;
} else if tags.is_any(
"building",
vec!["school", "university", "construction", "church"],
) {
return BuildingType::Empty;
} else if tags.is_any(
"building",
vec![
"garage",
"garages",
"shed",
"roof",
"greenhouse",
"farm_auxiliary",
"barn",
"service",
],
) {
return BuildingType::Empty;
} else if tags.is_any(
"building",
vec!["house", "detached", "semidetached_house", "farm"],
) {
residents = rng.gen_range(0..3);
} else if tags.is_any("building", vec!["hut", "static_caravan", "cabin"]) {
residents = rng.gen_range(0..2);
} else if tags.is_any("building", vec!["apartments", "terrace", "residential"]) {
residents = (area_sq_meters / 10.0) as usize;
} else {
residents = rng.gen_range(0..2);
}
if commercial && workers == 0 {
workers = (residents as f64 / 3.0) as usize;
}
if commercial {
if residents > 0 {
return BuildingType::ResidentialCommercial(residents, workers);
}
return BuildingType::Commercial(workers);
}
BuildingType::Residential {
num_residents: residents,
num_housing_units: 1,
}
}