use crate::{osm, LaneID, Map, PathConstraints, Position};
use abstutil::{
deserialize_btreemap, deserialize_usize, serialize_btreemap, serialize_usize, Tags,
};
use geom::{Distance, PolyLine, Polygon, Pt2D};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashSet, VecDeque};
use std::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct BuildingID(
#[serde(
serialize_with = "serialize_usize",
deserialize_with = "deserialize_usize"
)]
pub usize,
);
impl fmt::Display for BuildingID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Building #{}", self.0)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Building {
pub id: BuildingID,
pub polygon: Polygon,
pub address: String,
pub name: Option<NamePerLanguage>,
pub orig_id: osm::OsmID,
pub label_center: Pt2D,
pub amenities: BTreeSet<(NamePerLanguage, String)>,
pub bldg_type: BuildingType,
pub parking: OffstreetParking,
pub sidewalk_pos: Position,
pub driveway_geom: PolyLine,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum OffstreetParking {
PublicGarage(String, usize),
Private(usize, bool),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BuildingType {
Residential(usize),
ResidentialCommercial(usize, usize),
Commercial(usize),
Empty,
}
impl BuildingType {
pub fn has_residents(&self) -> bool {
match self {
BuildingType::Residential(_) | BuildingType::ResidentialCommercial(_, _) => true,
BuildingType::Commercial(_) | BuildingType::Empty => false,
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct NamePerLanguage(
#[serde(
serialize_with = "serialize_btreemap",
deserialize_with = "deserialize_btreemap"
)]
pub(crate) BTreeMap<Option<String>, String>,
);
impl NamePerLanguage {
pub fn get(&self, lang: Option<&String>) -> &String {
let lang = lang.cloned();
if let Some(name) = self.0.get(&lang) {
return name;
}
&self.0[&None]
}
pub fn new(tags: &Tags) -> Option<NamePerLanguage> {
let native_name = tags.get(osm::NAME)?;
let mut map = BTreeMap::new();
map.insert(None, native_name.to_string());
for (k, v) in tags.inner() {
if let Some(lang) = k.strip_prefix("name:") {
map.insert(Some(lang.to_string()), v.to_string());
}
}
Some(NamePerLanguage(map))
}
pub fn unnamed() -> NamePerLanguage {
let mut map = BTreeMap::new();
map.insert(None, "unnamed".to_string());
NamePerLanguage(map)
}
}
impl Building {
pub fn sidewalk(&self) -> LaneID {
self.sidewalk_pos.lane()
}
pub fn house_number(&self) -> Option<String> {
let num = self.address.split(" ").next().unwrap();
if num != "???" {
Some(num.to_string())
} else {
None
}
}
pub fn driving_connection(&self, map: &Map) -> Option<(Position, PolyLine)> {
let lane = map.get_parent(self.sidewalk()).find_closest_lane(
self.sidewalk(),
|l| PathConstraints::Car.can_use(l, map),
map,
)?;
let pos = self
.sidewalk_pos
.equiv_pos(lane, map)
.buffer_dist(Distance::meters(7.0), map)?;
Some((pos, self.driveway_geom.clone().must_push(pos.pt(map))))
}
pub fn biking_connection(&self, map: &Map) -> Option<(Position, Position)> {
if let Some(pair) = sidewalk_to_bike(self.sidewalk_pos, map) {
return Some(pair);
}
let mut queue: VecDeque<LaneID> = VecDeque::new();
let mut visited: HashSet<LaneID> = HashSet::new();
queue.push_back(self.sidewalk());
loop {
if queue.is_empty() {
return None;
}
let l = queue.pop_front().unwrap();
if visited.contains(&l) {
continue;
}
visited.insert(l);
if let Some(pair) = sidewalk_to_bike(Position::new(l, map.get_l(l).length() / 2.0), map)
{
return Some(pair);
}
for t in map.get_turns_from_lane(l) {
if !visited.contains(&t.id.dst) {
queue.push_back(t.id.dst);
}
}
}
}
pub fn num_parking_spots(&self) -> usize {
match self.parking {
OffstreetParking::PublicGarage(_, n) => n,
OffstreetParking::Private(n, _) => n,
}
}
}
fn sidewalk_to_bike(sidewalk_pos: Position, map: &Map) -> Option<(Position, Position)> {
let lane = map.get_parent(sidewalk_pos.lane()).find_closest_lane(
sidewalk_pos.lane(),
|l| !l.biking_blackhole && PathConstraints::Bike.can_use(l, map),
map,
)?;
Some((sidewalk_pos.equiv_pos(lane, map), sidewalk_pos))
}