ltn/
neighbourhood.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use maplit::btreeset;
4
5use geom::{ArrowCap, Distance, PolyLine, Polygon};
6use map_model::{osm, Direction, IntersectionID, Map, RoadID};
7use widgetry::{Drawable, EventCtx, GeomBatch};
8
9use crate::logic::{possible_destination_roads, CustomBoundary, Partitioning, Shortcuts};
10use crate::{is_private, App, NeighbourhoodID};
11
12// Once constructed, a Neighbourhood is immutable
13pub struct Neighbourhood {
14    pub id: NeighbourhoodID,
15
16    // Input
17    /// Intersections which form the boundary of the neighbourhood. This set includes any intersection which is
18    /// connected to a road which is part of the neighbourhood's perimeter.
19    /// The roads which form the perimeter of the neighbourhood are the union of `perimeter_roads` and `suspicious_perimeter_roads`.
20    pub borders: BTreeSet<IntersectionID>,
21    /// Intersections which are entirely inside the neighbourhood, and only connect interior roads to other interior roads.
22    pub interior_intersections: BTreeSet<IntersectionID>,
23    pub boundary_polygon: Polygon,
24
25    // Derived stuff
26    /// Roads which are either (a) entirely inside the neighbourhood and (b) roads which are part of `suspicious_perimeter_roads`.
27    pub interior_roads: BTreeSet<RoadID>,
28    /// Roads which form part of the neighbourhood's perimeter, and are classified as arterial roads based on their OSM tags.
29    /// `suspicious_perimeter_roads` are NOT included in `perimeter_roads`.
30    pub perimeter_roads: BTreeSet<RoadID>,
31    /// Roads which form part of the neighbourhood's perimeter, _**but**_ are classified as local roads based on their OSM tags.
32    /// `suspicious_perimeter_roads` are always a subset of `interior_roads`.
33    pub suspicious_perimeter_roads: BTreeSet<RoadID>,
34    /// Roads which are lie outside the `boundary_polygon` but could potentially be connected to an `interior_road` or
35    /// `perimeter_road` by either a `road.turn_restrictions`, or `road.complicated_turn_restrictions`. `finish_init()` populates
36    /// this field.
37    pub connected_exterior_roads: BTreeSet<RoadID>,
38
39    pub cells: Vec<Cell>,
40    pub shortcuts: Shortcuts,
41}
42
43/// A partitioning of the interior of a neighbourhood based on driving connectivity
44pub struct Cell {
45    /// Most roads are fully in one cell. Roads with modal filters on them are sometimes split
46    /// between two cells, and the DistanceInterval indicates the split. The distances are over the
47    /// road's center line length.
48    pub roads: BTreeMap<RoadID, DistanceInterval>,
49    /// Intersections where this cell touches the boundary of the neighbourhood.
50    pub borders: BTreeSet<IntersectionID>,
51}
52
53impl Cell {
54    /// A cell is disconnected if it's not connected to a perimeter road.
55    pub fn is_disconnected(&self) -> bool {
56        self.borders.is_empty()
57    }
58
59    pub fn border_arrows(&self, app: &App) -> Vec<Polygon> {
60        let mut arrows = Vec::new();
61        for i in &self.borders {
62            // Most borders only have one road in the interior of the neighbourhood. Draw an arrow
63            // for each of those. If there happen to be multiple interior roads for one border, the
64            // arrows will overlap each other -- but that happens anyway with borders close
65            // together at certain angles.
66            for r in self.roads.keys() {
67                let road = app.per_map.map.get_r(*r);
68                // Design choice: when we have a filter right at the entrance of a neighbourhood, it
69                // creates its own little cell allowing access to just the very beginning of the
70                // road. Let's not draw anything for that.
71                if road.modal_filter.is_some() {
72                    continue;
73                }
74
75                // Find the angle pointing into the neighbourhood
76                let angle_in = if road.src_i == *i {
77                    road.center_pts.first_line().angle()
78                } else if road.dst_i == *i {
79                    road.center_pts.last_line().angle().opposite()
80                } else {
81                    // This interior road isn't connected to this border
82                    continue;
83                };
84
85                let center = app.per_map.map.get_i(*i).polygon.center();
86                let pt_farther = center.project_away(Distance::meters(40.0), angle_in.opposite());
87                let pt_closer = center.project_away(Distance::meters(10.0), angle_in.opposite());
88
89                // The arrow direction depends on if the road is one-way
90                let thickness = Distance::meters(6.0);
91                if let Some(dir) = road.oneway_for_driving() {
92                    let pl = if road.src_i == *i {
93                        PolyLine::must_new(vec![pt_farther, pt_closer])
94                    } else {
95                        PolyLine::must_new(vec![pt_closer, pt_farther])
96                    };
97                    arrows.push(
98                        pl.maybe_reverse(dir == Direction::Back)
99                            .make_arrow(thickness, ArrowCap::Triangle),
100                    );
101                } else {
102                    // Order doesn't matter
103                    arrows.push(
104                        PolyLine::must_new(vec![pt_closer, pt_farther])
105                            .make_double_arrow(thickness, ArrowCap::Triangle),
106                    );
107                }
108            }
109        }
110        arrows
111    }
112}
113
114/// An interval along a road's length, with start < end.
115pub struct DistanceInterval {
116    pub start: Distance,
117    pub end: Distance,
118}
119
120impl Neighbourhood {
121    pub fn new(app: &App, id: NeighbourhoodID) -> Neighbourhood {
122        Self::new_without_app(&app.per_map.map, app.partitioning(), id)
123    }
124
125    pub fn new_without_app(
126        map: &Map,
127        partitioning: &Partitioning,
128        id: NeighbourhoodID,
129    ) -> Neighbourhood {
130        if let Some(custom) = partitioning.custom_boundaries.get(&id) {
131            return Self::new_custom(map, id, custom.clone());
132        }
133
134        let orig_perimeter = partitioning.neighbourhood_block(id).perimeter.clone();
135
136        let mut n = Neighbourhood {
137            id,
138            interior_roads: orig_perimeter.interior.clone(),
139            perimeter_roads: BTreeSet::new(),
140            borders: BTreeSet::new(),
141            interior_intersections: BTreeSet::new(),
142            boundary_polygon: Polygon::dummy(),
143            suspicious_perimeter_roads: BTreeSet::new(),
144            connected_exterior_roads: BTreeSet::new(),
145
146            cells: Vec::new(),
147            shortcuts: Shortcuts::empty(),
148        };
149
150        // The neighbourhood's perimeter hugs the "interior" of the neighbourhood. If we just use
151        // the other side of the perimeter road, the highlighted area nicely shows the boundary
152        // road too. (But sometimes this breaks, of course)
153        n.boundary_polygon = match orig_perimeter.clone().flip_side_of_road().to_block(map) {
154            Ok(block) => block.polygon,
155            Err(_) => orig_perimeter.clone().to_block(map).unwrap().polygon,
156        };
157        if let Some(polygon) = partitioning.get_info(id).override_drawing_boundary.clone() {
158            n.boundary_polygon = polygon;
159        }
160
161        for id in &orig_perimeter.roads {
162            let road = map.get_r(id.road);
163            // Part of the perimeter may be a local road. This is all it takes to correct cell and
164            // shortcut calculation, and allow edits on local perimeter roads.
165            if road.get_rank() == osm::RoadRank::Local {
166                n.interior_roads.insert(road.id);
167                n.suspicious_perimeter_roads.insert(road.id);
168            } else {
169                n.perimeter_roads.insert(road.id);
170                n.borders.insert(road.src_i);
171                n.borders.insert(road.dst_i);
172            }
173        }
174
175        n.finish_init(map);
176        n
177    }
178
179    fn new_custom(map: &Map, id: NeighbourhoodID, custom: CustomBoundary) -> Neighbourhood {
180        let mut n = Neighbourhood {
181            id,
182            interior_roads: custom.interior_roads,
183            // TODO Don't know how to calculate these
184            perimeter_roads: BTreeSet::new(),
185            borders: custom.borders,
186            interior_intersections: BTreeSet::new(),
187            boundary_polygon: custom.boundary_polygon,
188            suspicious_perimeter_roads: BTreeSet::new(),
189            connected_exterior_roads: BTreeSet::new(),
190
191            cells: Vec::new(),
192            shortcuts: Shortcuts::empty(),
193        };
194        n.finish_init(map);
195        n
196    }
197
198    fn finish_init(&mut self, map: &Map) {
199        for r in &self.interior_roads {
200            let road = map.get_r(*r);
201            for i in [road.src_i, road.dst_i] {
202                if !self.borders.contains(&i) {
203                    self.interior_intersections.insert(i);
204                }
205            }
206        }
207
208        // Add every connected road into connected_exterior_roads
209        let mut exterior: BTreeSet<RoadID> = BTreeSet::new();
210        for r in [&self.perimeter_roads, &self.interior_roads]
211            .into_iter()
212            .flatten()
213        {
214            exterior.extend(possible_destination_roads(map, *r, None));
215        }
216
217        debug!(
218            "BUILDING CONNECTED_EXTERIOR_ROADS: exterior.len() = {}",
219            exterior.len()
220        );
221        debug!(
222            "BUILDING CONNECTED_EXTERIOR_ROADS: perimeter_roads.len() = {}",
223            &self.perimeter_roads.len()
224        );
225        debug!(
226            "BUILDING CONNECTED_EXTERIOR_ROADS: interior_roads.len() = {}",
227            &self.interior_roads.len()
228        );
229
230        // Now remove the interior and perimeter roads
231        exterior.retain(|r| !self.perimeter_roads.contains(r) & !self.interior_roads.contains(r));
232        self.connected_exterior_roads = exterior;
233
234        debug!(
235            "BUILDING CONNECTED_EXTERIOR_ROADS: connected_exterior_roads.len() = {}",
236            &self.connected_exterior_roads.len()
237        );
238
239        self.edits_changed(map);
240    }
241
242    /// Recalculates cells and shortcuts after a relevant edit
243    pub fn edits_changed(&mut self, map: &Map) {
244        self.cells = find_cells(map, &self.interior_roads, &self.borders);
245
246        // TODO The timer could be nice for large areas. But plumbing through one everywhere is
247        // tedious, and would hit a nested start_iter bug anyway.
248        self.shortcuts = Shortcuts::new(map, self, &mut abstutil::Timer::throwaway());
249    }
250
251    pub fn fade_irrelevant(&self, ctx: &EventCtx, app: &App) -> Drawable {
252        let fade_area = Polygon::with_holes(
253            app.per_map
254                .map
255                .get_boundary_polygon()
256                .get_outer_ring()
257                .clone(),
258            vec![self.boundary_polygon.clone().into_outer_ring()],
259        );
260        GeomBatch::from(vec![(app.cs.fade_map_dark, fade_area)]).upload(ctx)
261    }
262}
263
264// Find all of the disconnected "cells" of reachable areas, bounded by border intersections. This is with
265// respect to driving.
266fn find_cells(
267    map: &Map,
268    interior_roads: &BTreeSet<RoadID>,
269    borders: &BTreeSet<IntersectionID>,
270) -> Vec<Cell> {
271    let mut cells = Vec::new();
272    let mut visited = BTreeSet::new();
273
274    for start in interior_roads {
275        if visited.contains(start) || map.get_r(*start).modal_filter.is_some() {
276            continue;
277        }
278        let start = *start;
279        let road = map.get_r(start);
280        // Just skip entirely; they're invisible for the purpose of dividing into cells
281        if !crate::is_driveable(road, map) {
282            continue;
283        }
284        // There are non-private roads connected only to private roads, like
285        // https://www.openstreetmap.org/way/725759378 and
286        // https://www.openstreetmap.org/way/27890699. Also skip these, to avoid creating a
287        // disconnected cell.
288        let connected_to_public_road = [road.src_i, road.dst_i]
289            .into_iter()
290            .flat_map(|i| &map.get_i(i).roads)
291            .any(|r| *r != start && !is_private(map.get_r(*r)));
292        if !connected_to_public_road {
293            continue;
294        }
295
296        let cell = floodfill(map, start, borders, interior_roads);
297        visited.extend(cell.roads.keys().cloned());
298
299        cells.push(cell);
300    }
301
302    // Filtered roads right along the perimeter have a tiny cell
303    for (road, filter) in map.all_roads_with_modal_filter() {
304        if borders.contains(&road.src_i) {
305            let mut cell = Cell {
306                roads: BTreeMap::new(),
307                borders: btreeset! { road.src_i },
308            };
309            cell.roads.insert(
310                road.id,
311                DistanceInterval {
312                    start: Distance::ZERO,
313                    end: filter.dist,
314                },
315            );
316            cells.push(cell);
317        }
318        if borders.contains(&road.dst_i) {
319            let mut cell = Cell {
320                roads: BTreeMap::new(),
321                borders: btreeset! { road.dst_i },
322            };
323            cell.roads.insert(
324                road.id,
325                DistanceInterval {
326                    start: filter.dist,
327                    end: road.length(),
328                },
329            );
330            cells.push(cell);
331        }
332    }
333
334    cells
335}
336
337fn floodfill(
338    map: &Map,
339    start: RoadID,
340    neighbourhood_borders: &BTreeSet<IntersectionID>,
341    interior_roads: &BTreeSet<RoadID>,
342) -> Cell {
343    let mut visited_roads: BTreeMap<RoadID, DistanceInterval> = BTreeMap::new();
344    let mut cell_borders = BTreeSet::new();
345    // We don't need a priority queue
346    let mut queue = vec![start];
347
348    // The caller should handle this case
349    assert!(map.get_r(start).modal_filter.is_none());
350    assert!(crate::is_driveable(map.get_r(start), map));
351
352    while !queue.is_empty() {
353        let current = map.get_r(queue.pop().unwrap());
354        if visited_roads.contains_key(&current.id) {
355            continue;
356        }
357        visited_roads.insert(
358            current.id,
359            DistanceInterval {
360                start: Distance::ZERO,
361                end: current.length(),
362            },
363        );
364
365        for i in [current.src_i, current.dst_i] {
366            // It's possible for one border intersection to have two roads in the interior of the
367            // neighbourhood. Don't consider a turn between those roads through this intersection as
368            // counting as connectivity -- we're right at the boundary road, so it's like leaving
369            // and re-entering the neighbourhood.
370            if neighbourhood_borders.contains(&i) {
371                cell_borders.insert(i);
372                continue;
373            }
374
375            for next in &map.get_i(i).roads {
376                let next_road = map.get_r(*next);
377                if let Some(ref filter) = map.get_i(i).modal_filter {
378                    if !filter.allows_turn(current.id, *next) {
379                        continue;
380                    }
381                }
382                if let Some(ref filter) = map.get_r(*next).modal_filter {
383                    // Which ends of the filtered road have we reached?
384                    let mut visited_start = next_road.src_i == i;
385                    let mut visited_end = next_road.dst_i == i;
386                    // We may have visited previously from the other side.
387                    if let Some(interval) = visited_roads.get(next) {
388                        if interval.start == Distance::ZERO {
389                            visited_start = true;
390                        }
391                        if interval.end == next_road.length() {
392                            visited_end = true;
393                        }
394                    }
395                    visited_roads.insert(
396                        *next,
397                        DistanceInterval {
398                            start: if visited_start {
399                                Distance::ZERO
400                            } else {
401                                filter.dist
402                            },
403                            end: if visited_end {
404                                next_road.length()
405                            } else {
406                                filter.dist
407                            },
408                        },
409                    );
410                    continue;
411                }
412
413                if !crate::is_driveable(next_road, map) {
414                    continue;
415                }
416                // TODO This happens near weird geometry. This is OK, but should root-cause it.
417                if !interior_roads.contains(next) {
418                    error!("A cell leaked out to {next} from {i}");
419                    continue;
420                }
421
422                queue.push(*next);
423            }
424        }
425    }
426
427    Cell {
428        roads: visited_roads,
429        borders: cell_borders,
430    }
431}