map_model/objects/
building.rs

1use std::collections::{HashSet, VecDeque};
2use std::fmt;
3
4use serde::{Deserialize, Serialize};
5
6use abstutil::{deserialize_usize, serialize_usize, Tags};
7use geom::{Distance, PolyLine, Polygon, Pt2D};
8
9use crate::{osm, Amenity, AmenityType, LaneID, Map, NamePerLanguage, PathConstraints, Position};
10
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
12pub struct BuildingID(
13    #[serde(
14        serialize_with = "serialize_usize",
15        deserialize_with = "deserialize_usize"
16    )]
17    pub usize,
18);
19
20impl fmt::Display for BuildingID {
21    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22        write!(f, "Building #{}", self.0)
23    }
24}
25
26/// A building has connections to the road and sidewalk, may contain commercial amenities, and have
27/// off-street parking.
28#[derive(Serialize, Deserialize, Clone, Debug)]
29pub struct Building {
30    pub id: BuildingID,
31    pub polygon: Polygon,
32    pub levels: f64,
33    pub address: String,
34    pub name: Option<NamePerLanguage>,
35    pub orig_id: osm::OsmID,
36    /// Where a text label should be centered to have the best chances of being contained within
37    /// the polygon.
38    pub label_center: Pt2D,
39    pub amenities: Vec<Amenity>,
40    pub bldg_type: BuildingType,
41    pub parking: OffstreetParking,
42    /// Depending on options while importing, these might be empty, to save file space.
43    pub osm_tags: Tags,
44
45    /// The building's connection for any agent can change based on map edits. Just store the one
46    /// for pedestrians and lazily calculate the others.
47    pub sidewalk_pos: Position,
48    /// Goes from building to sidewalk
49    pub driveway_geom: PolyLine,
50}
51
52/// Represent no parking as Private(0, false).
53#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
54pub enum OffstreetParking {
55    /// (Name, spots)
56    PublicGarage(String, usize),
57    /// (Spots, explicitly tagged as a garage)
58    Private(usize, bool),
59}
60
61#[derive(Serialize, Deserialize, Clone, Debug)]
62pub enum BuildingType {
63    Residential {
64        num_residents: usize,
65        num_housing_units: usize,
66    },
67    /// An estimated number of residents, workers
68    ResidentialCommercial(usize, usize),
69    /// An estimated number of workers
70    Commercial(usize),
71    Empty,
72}
73
74impl BuildingType {
75    pub fn has_residents(&self) -> bool {
76        match self {
77            BuildingType::Residential { .. } | BuildingType::ResidentialCommercial(_, _) => true,
78            BuildingType::Commercial(_) | BuildingType::Empty => false,
79        }
80    }
81}
82
83impl Building {
84    pub fn sidewalk(&self) -> LaneID {
85        self.sidewalk_pos.lane()
86    }
87
88    /// The polyline goes from the building to the driving position
89    // TODO Make this handle parking_blackhole
90    pub fn driving_connection(&self, map: &Map) -> Option<(Position, PolyLine)> {
91        let lane = map
92            .get_parent(self.sidewalk())
93            .find_closest_lane(self.sidewalk(), |l| PathConstraints::Car.can_use(l, map))?;
94        // TODO Do we need to insist on this buffer, now that we can make cars gradually appear?
95        let pos = self
96            .sidewalk_pos
97            .equiv_pos(lane, map)
98            .buffer_dist(Distance::meters(7.0), map)?;
99        Some((pos, self.driveway_geom.clone().optionally_push(pos.pt(map))))
100    }
101
102    /// Returns (biking position, sidewalk position). Could fail if the biking graph is
103    /// disconnected.
104    pub fn biking_connection(&self, map: &Map) -> Option<(Position, Position)> {
105        // Easy case: the building is directly next to a usable lane
106        if let Some(pair) = sidewalk_to_bike(self.sidewalk_pos, map) {
107            return Some(pair);
108        }
109
110        // Floodfill the sidewalk graph until we find a sidewalk<->bike connection.
111        let mut queue: VecDeque<LaneID> = VecDeque::new();
112        let mut visited: HashSet<LaneID> = HashSet::new();
113        queue.push_back(self.sidewalk());
114
115        loop {
116            if queue.is_empty() {
117                return None;
118            }
119            let l = queue.pop_front().unwrap();
120            if visited.contains(&l) {
121                continue;
122            }
123            visited.insert(l);
124            // TODO Could search by sidewalk endpoint
125            if let Some(pair) = sidewalk_to_bike(Position::new(l, map.get_l(l).length() / 2.0), map)
126            {
127                return Some(pair);
128            }
129            for (_, next) in map.get_next_turns_and_lanes(l) {
130                if next.is_walkable() && !visited.contains(&next.id) {
131                    queue.push_back(next.id);
132                }
133            }
134        }
135    }
136
137    pub fn num_parking_spots(&self) -> usize {
138        match self.parking {
139            OffstreetParking::PublicGarage(_, n) => n,
140            OffstreetParking::Private(n, _) => n,
141        }
142    }
143
144    /// Does this building contain any amenity matching the category?
145    pub fn has_amenity(&self, category: AmenityType) -> bool {
146        for amenity in &self.amenities {
147            if AmenityType::categorize(&amenity.amenity_type) == Some(category) {
148                return true;
149            }
150        }
151        false
152    }
153}
154
155fn sidewalk_to_bike(sidewalk_pos: Position, map: &Map) -> Option<(Position, Position)> {
156    let lane = map
157        .get_parent(sidewalk_pos.lane())
158        .find_closest_lane(sidewalk_pos.lane(), |l| {
159            !l.biking_blackhole && PathConstraints::Bike.can_use(l, map)
160        })?;
161    // No buffer needed
162    Some((sidewalk_pos.equiv_pos(lane, map), sidewalk_pos))
163}