blockfinding/lib.rs
1#[macro_use]
2extern crate anyhow;
3#[macro_use]
4extern crate log;
5
6use std::collections::{BTreeSet, HashMap, HashSet};
7use std::fmt;
8
9use anyhow::Result;
10use serde::{Deserialize, Serialize};
11
12use abstutil::wraparound_get;
13use geom::{Polygon, Pt2D, Ring};
14
15use map_model::{
16 osm, CommonEndpoint, Direction, LaneID, Map, PathConstraints, RoadID, RoadSideID, SideOfRoad,
17};
18
19/// A block is defined by a perimeter that traces along the sides of roads. Inside the perimeter,
20/// the block may contain buildings and interior roads. In the simple case, a block represents a
21/// single "city block", with no interior roads. It may also cover a "neighborhood", where the
22/// perimeter contains some "major" and the interior consists only of "minor" roads.
23// TODO Maybe "block" is a misleading term. "Contiguous road trace area"?
24#[derive(Clone, Serialize, Deserialize)]
25pub struct Block {
26 pub perimeter: Perimeter,
27 /// The polygon covers the interior of the block.
28 pub polygon: Polygon,
29}
30
31/// A sequence of roads in order, beginning and ending at the same place. No "crossings" -- tracing
32/// along this sequence should geometrically yield a simple polygon.
33// TODO Handle the map boundary. Sometimes this perimeter should be broken up by border
34// intersections or possibly by water/park areas.
35#[derive(Clone, Serialize, Deserialize)]
36pub struct Perimeter {
37 pub roads: Vec<RoadSideID>,
38 /// These roads exist entirely within the perimeter
39 pub interior: BTreeSet<RoadID>,
40}
41
42impl Perimeter {
43 /// Starting at any lane, snap to the nearest side of that road, then begin tracing a single
44 /// block, with no interior roads. This will fail if a map boundary is reached. The results are
45 /// unusual when crossing the entrance to a tunnel or bridge, and so `skip` is used to avoid
46 /// tracing there.
47 pub fn single_block(map: &Map, start: LaneID, skip: &HashSet<RoadID>) -> Result<Perimeter> {
48 let mut roads = Vec::new();
49 let start_road_side = map.get_l(start).get_nearest_side_of_road(map);
50
51 if skip.contains(&start_road_side.road) {
52 bail!("Started on a road we shouldn't trace");
53 }
54
55 // We may start on a loop road on the "inner" direction
56 {
57 let start_r = map.get_parent(start);
58 if start_r.src_i == start_r.dst_i {
59 let i = map.get_i(start_r.src_i);
60 if !i.get_road_sides_sorted(map).contains(&start_road_side) {
61 bail!("Starting on inner piece of a loop road");
62 }
63 }
64 }
65
66 // We need to track which side of the road we're at, but also which direction we're facing
67 let mut current_road_side = start_road_side;
68 let mut current_intersection = map.get_l(start).dst_i;
69 loop {
70 let i = map.get_i(current_intersection);
71 if i.is_border() {
72 bail!("hit the map boundary at {}", i.orig_id);
73 }
74 let mut sorted_roads = i.get_road_sides_sorted(map);
75 sorted_roads.retain(|id| !skip.contains(&id.road));
76
77 let idx = sorted_roads
78 .iter()
79 .position(|x| *x == current_road_side)
80 .unwrap() as isize;
81 // Do we go clockwise or counter-clockwise around the intersection? Well, unless we're
82 // at a dead-end, we want to avoid the other side of the same road.
83 let mut next = *wraparound_get(&sorted_roads, idx + 1);
84 assert_ne!(next, current_road_side);
85 if next.road == current_road_side.road {
86 next = *wraparound_get(&sorted_roads, idx - 1);
87 assert_ne!(next, current_road_side);
88 if next.road == current_road_side.road {
89 if sorted_roads.len() != 2 {
90 bail!("Looped back on the same road, but not at a dead-end");
91 }
92 }
93 }
94 roads.push(current_road_side);
95 current_road_side = next;
96 current_intersection = map
97 .get_r(current_road_side.road)
98 .other_endpt(current_intersection);
99
100 if current_road_side == start_road_side {
101 roads.push(start_road_side);
102 break;
103 }
104
105 if roads.len() > map.all_roads().len() {
106 bail!(
107 "Infinite loop starting from {start} ({})",
108 map.get_parent(start).orig_id
109 );
110 }
111 }
112 assert_eq!(roads[0], *roads.last().unwrap());
113 Ok(Perimeter {
114 roads,
115 interior: BTreeSet::new(),
116 })
117 }
118
119 /// This calculates all single block perimeters for the entire map. The resulting list does not
120 /// cover roads near the map boundary.
121 pub fn find_all_single_blocks(map: &Map) -> Vec<Perimeter> {
122 let skip = Perimeter::find_roads_to_skip_tracing(map);
123
124 let mut seen = HashSet::new();
125 let mut perimeters = Vec::new();
126 for lane in map.all_lanes() {
127 let side = lane.get_nearest_side_of_road(map);
128 if seen.contains(&side) {
129 continue;
130 }
131 match Perimeter::single_block(map, lane.id, &skip) {
132 Ok(perimeter) => {
133 seen.extend(perimeter.roads.clone());
134 perimeters.push(perimeter);
135 }
136 Err(err) => {
137 // The logs are quite spammy and not helpful yet, since they're all expected
138 // cases near the map boundary
139 if false {
140 warn!("Failed from {}: {}", lane.id, err);
141 }
142 // Don't try again
143 seen.insert(side);
144 }
145 }
146 }
147 perimeters
148 }
149
150 /// Blockfinding is specialized for the LTN tool, so non-driveable roads (cycleways and light
151 /// rail) are considered invisible and can't be on a perimeter. Previously, there were also
152 /// some heuristics here to try to skip certain bridges/tunnels that break the planarity of
153 /// blocks.
154 pub fn find_roads_to_skip_tracing(map: &Map) -> HashSet<RoadID> {
155 let mut skip = HashSet::new();
156 for r in map.all_roads() {
157 // TODO Redundant
158 if r.is_light_rail() {
159 skip.insert(r.id);
160 } else if !PathConstraints::Car.can_use_road(r, map) {
161 skip.insert(r.id);
162 } else if r.orig_id.osm_way_id == osm::WayID(700169412)
163 || r.orig_id.osm_way_id == osm::WayID(2800303)
164 {
165 // TODO Hack to avoid the A27 bridge in Chichester
166 // Note that https://www.openstreetmap.org/way/700169409 isn't the ID to skip,
167 // because osm2streets merges roads, and this ID arbitrarily disappears here
168 skip.insert(r.id);
169 }
170 }
171 skip
172 }
173
174 /// A perimeter has the first and last road matching up, but that's confusing to
175 /// work with. Temporarily undo that.
176 fn undo_invariant(&mut self) {
177 assert_eq!(Some(self.roads[0]), self.roads.pop());
178 }
179
180 /// Restore the first=last invariant. Methods may temporarily break this, but must restore it
181 /// before returning.
182 fn restore_invariant(&mut self) {
183 self.roads.push(self.roads[0]);
184 }
185
186 /// Try to merge two blocks. This'll only succeed when the blocks are adjacent, but the merge
187 /// wouldn't create an interior "hole".
188 ///
189 /// Note this always modifies both perimeters, even upon failure. The caller should copy the
190 /// input and only use the output upon success.
191 fn try_to_merge(
192 &mut self,
193 map: &Map,
194 other: &mut Perimeter,
195 debug_failures: bool,
196 ) -> Result<()> {
197 for reverse_to_fix_winding_order in [false, true] {
198 self.undo_invariant();
199 other.undo_invariant();
200
201 // Calculate common roads
202 let roads1: HashSet<RoadID> = self.roads.iter().map(|id| id.road).collect();
203 let roads2: HashSet<RoadID> = other.roads.iter().map(|id| id.road).collect();
204 let common: HashSet<RoadID> = roads1.intersection(&roads2).cloned().collect();
205 if common.is_empty() {
206 if debug_failures {
207 warn!("No common roads");
208 }
209 bail!("No common roads");
210 }
211
212 // "Rotate" the order of roads, so that all of the overlapping roads are at the end of the
213 // list. If the entire perimeter is surrounded by the other, then no rotation needed.
214 if self.roads.len() != common.len() {
215 let mut i = 0;
216 while common.contains(&self.roads[0].road)
217 || !common.contains(&self.roads.last().unwrap().road)
218 {
219 self.roads.rotate_left(1);
220
221 i += 1;
222 if i == self.roads.len() {
223 bail!(
224 "Rotating {:?} against common {:?} infinite-looped",
225 self.roads,
226 common
227 );
228 }
229 }
230 }
231 // Same thing with the other
232 if other.roads.len() != common.len() {
233 let mut i = 0;
234 while common.contains(&other.roads[0].road)
235 || !common.contains(&other.roads.last().unwrap().road)
236 {
237 other.roads.rotate_left(1);
238
239 i += 1;
240 if i == other.roads.len() {
241 bail!(
242 "Rotating {:?} against common {:?} infinite-looped",
243 self.roads,
244 common
245 );
246 }
247 }
248 }
249
250 if debug_failures {
251 println!("\nCommon: {:?}\n{:?}\n{:?}", common, self, other);
252 }
253
254 if !reverse_to_fix_winding_order && self.reverse_to_fix_winding_order(map, other) {
255 // Revert, reverse one, and try again.
256 self.restore_invariant();
257 other.restore_invariant();
258 self.roads.reverse();
259 continue;
260 }
261
262 // Check if all of the common roads are at the end of each perimeter, so we can
263 // "blindly" do this snipping. If this isn't true, then the overlapping portions are
264 // split by non-overlapping roads. This happens when merging the two blocks would
265 // result in a "hole."
266 for id in self.roads.iter().rev().take(common.len()) {
267 if !common.contains(&id.road) {
268 if debug_failures {
269 warn!(
270 "The common roads on the first aren't consecutive, near {:?}",
271 id
272 );
273 }
274 bail!(
275 "The common roads on the first aren't consecutive, near {:?}",
276 id
277 );
278 }
279 }
280 for id in other.roads.iter().rev().take(common.len()) {
281 if !common.contains(&id.road) {
282 if debug_failures {
283 warn!(
284 "The common roads on the second aren't consecutive, near {:?}",
285 id
286 );
287 }
288 bail!(
289 "The common roads on the first aren't consecutive, near {:?}",
290 id
291 );
292 }
293 }
294
295 // Very straightforward snipping now
296 for _ in 0..common.len() {
297 self.roads.pop().unwrap();
298 other.roads.pop().unwrap();
299 }
300
301 // This order assumes everything is clockwise to start with.
302 self.roads.append(&mut other.roads);
303
304 // TODO This case was introduced with find_roads_to_skip_tracing. Not sure why.
305 if self.roads.is_empty() {
306 if debug_failures {
307 warn!("Two perimeters had every road in common: {:?}", common);
308 }
309 bail!("Two perimeters had every road in common: {:?}", common);
310 }
311
312 self.interior.extend(common);
313 self.interior.append(&mut other.interior);
314
315 // Restore the first=last invariant
316 self.restore_invariant();
317
318 // Make sure we didn't wind up with any internal dead-ends
319 self.collapse_deadends();
320
321 if let Err(err) = self.check_continuity(map) {
322 debug!(
323 "A merged perimeter couldn't be blockified: {}. {:?}",
324 err, self
325 );
326 bail!(
327 "A merged perimeter couldn't be blockified: {}. {:?}",
328 err,
329 self
330 );
331 }
332
333 return Ok(());
334 }
335 unreachable!()
336 }
337
338 fn check_continuity(&self, map: &Map) -> Result<()> {
339 for pair in self.roads.windows(2) {
340 let r1 = map.get_r(pair[0].road);
341 let r2 = map.get_r(pair[1].road);
342 if r1.common_endpoint(r2) == CommonEndpoint::None {
343 bail!("Part of the perimeter goes from {:?} to {:?}, but they don't share a common endpoint", pair[0], pair[1]);
344 }
345 }
346 Ok(())
347 }
348
349 /// Should we reverse one perimeter to match the winding order?
350 ///
351 /// This is only meant to be called in the middle of try_to_merge. It assumes both perimeters
352 /// have already been rotated so the common roads are at the end. The invariant of first=last
353 /// is not true.
354 fn reverse_to_fix_winding_order(&self, map: &Map, other: &Perimeter) -> bool {
355 // Using geometry to determine winding order is brittle. Look for any common road, and see
356 // where it points.
357 let common_example = self.roads.last().unwrap().road;
358 let last_common_for_self = match map
359 .get_r(common_example)
360 .common_endpoint(map.get_r(wraparound_get(&self.roads, self.roads.len() as isize).road))
361 {
362 CommonEndpoint::One(i) => i,
363 CommonEndpoint::Both => {
364 // If the common road is a loop on the intersection, then this perimeter must be of
365 // length 2 (or 3 with the invariant), and reversing it is meaningless.
366 return false;
367 }
368 CommonEndpoint::None => unreachable!(),
369 };
370
371 // Find the same road in the other perimeter
372 let other_idx = other
373 .roads
374 .iter()
375 .position(|x| x.road == common_example)
376 .unwrap() as isize;
377 let last_common_for_other = match map
378 .get_r(common_example)
379 .common_endpoint(map.get_r(wraparound_get(&other.roads, other_idx + 1).road))
380 {
381 CommonEndpoint::One(i) => i,
382 CommonEndpoint::Both => {
383 return false;
384 }
385 CommonEndpoint::None => unreachable!(),
386 };
387 last_common_for_self == last_common_for_other
388 }
389
390 /// Try to merge all given perimeters. If successful, only one perimeter will be returned.
391 /// Perimeters are never "destroyed" -- if not merged, they'll appear in the results. If
392 /// `stepwise_debug` is true, returns after performing just one merge.
393 pub fn merge_all(map: &Map, mut input: Vec<Perimeter>, stepwise_debug: bool) -> Vec<Perimeter> {
394 // Internal dead-ends break merging, so first collapse of those. Do this before even
395 // looking for neighbors, since find_common_roads doesn't understand dead-ends.
396 for p in &mut input {
397 p.collapse_deadends();
398 }
399
400 loop {
401 let mut debug = false;
402 let mut results: Vec<Perimeter> = Vec::new();
403 let num_input = input.len();
404 'INPUT: for perimeter in input {
405 if debug {
406 results.push(perimeter);
407 continue;
408 }
409
410 for other in &mut results {
411 // TODO Due to https://github.com/a-b-street/abstreet/issues/841, it seems like
412 // rotation sometimes breaks `to_block`, so for now, always revert to the
413 // original upon failure.
414 let mut copy_a = other.clone();
415 let mut copy_b = perimeter.clone();
416 if let Ok(()) = copy_a.try_to_merge(map, &mut copy_b, stepwise_debug) {
417 *other = copy_a;
418
419 // To debug, return after any single change
420 debug = stepwise_debug;
421 continue 'INPUT;
422 }
423 }
424
425 // No match
426 results.push(perimeter);
427 }
428
429 // Should we try merging again?
430 if results.len() > 1 && results.len() < num_input && !stepwise_debug {
431 input = results;
432 continue;
433 }
434 return results;
435 }
436 }
437
438 /// If the perimeter follows any dead-end roads, "collapse" them and instead make the perimeter
439 /// contain the dead-end.
440 pub fn collapse_deadends(&mut self) {
441 // Repeatedly try to do this as long as something is changing.
442 loop {
443 let orig = self.clone();
444 self.undo_invariant();
445
446 // TODO Workaround https://github.com/a-b-street/abstreet/issues/834. If this is a loop
447 // around a disconnected fragment of road, don't touch it
448 if self.roads.len() == 2 && self.roads[0].road == self.roads[1].road {
449 self.restore_invariant();
450 return;
451 }
452
453 // If the dead-end straddles the loop, it's confusing. Just rotate until that's not true.
454 while self.roads[0].road == self.roads.last().unwrap().road {
455 self.roads.rotate_left(1);
456 }
457
458 // TODO This won't handle a deadend that's more than 1 segment long
459 let mut roads: Vec<RoadSideID> = Vec::new();
460 let mut changed = false;
461 for id in self.roads.drain(..) {
462 if Some(id.road) == roads.last().map(|id| id.road) {
463 changed = true;
464 roads.pop();
465 self.interior.insert(id.road);
466 } else {
467 roads.push(id);
468 }
469 }
470
471 self.roads = roads;
472 if self.roads.is_empty() {
473 // TODO This case was introduced with find_roads_to_skip_tracing. Not sure why.
474 *self = orig;
475 return;
476 }
477 self.restore_invariant();
478
479 if !changed {
480 return;
481 }
482 }
483 }
484
485 /// Consider the perimeters as a graph, with adjacency determined by sharing any road in common.
486 /// Partition adjacent perimeters, subject to the predicate. Each partition should produce a
487 /// single result with `merge_all`.
488 pub fn partition_by_predicate<F: Fn(RoadID) -> bool>(
489 input: Vec<Perimeter>,
490 predicate: F,
491 ) -> Vec<Vec<Perimeter>> {
492 let mut road_to_perimeters: HashMap<RoadID, Vec<usize>> = HashMap::new();
493 for (idx, perimeter) in input.iter().enumerate() {
494 for id in &perimeter.roads {
495 road_to_perimeters
496 .entry(id.road)
497 .or_insert_with(Vec::new)
498 .push(idx);
499 }
500 }
501
502 // Start at one perimeter, floodfill to adjacent perimeters, subject to the predicate.
503 // Returns the indices of everything in that component.
504 let floodfill = |start: usize| -> BTreeSet<usize> {
505 let mut visited = BTreeSet::new();
506 let mut queue = vec![start];
507 while !queue.is_empty() {
508 let current = queue.pop().unwrap();
509 if visited.contains(¤t) {
510 continue;
511 }
512 visited.insert(current);
513 for id in &input[current].roads {
514 if predicate(id.road) {
515 queue.extend(road_to_perimeters[&id.road].clone());
516 }
517 }
518 }
519 visited
520 };
521
522 let mut partitions: Vec<BTreeSet<usize>> = Vec::new();
523 let mut finished: HashSet<usize> = HashSet::new();
524 for start in 0..input.len() {
525 if finished.contains(&start) {
526 continue;
527 }
528 let partition = floodfill(start);
529 finished.extend(partition.clone());
530 partitions.push(partition);
531 }
532
533 // Map the indices back to the actual perimeters.
534 let mut perimeters: Vec<Option<Perimeter>> = input.into_iter().map(Some).collect();
535 let mut results = Vec::new();
536 for indices in partitions {
537 let mut partition = Vec::new();
538 for idx in indices {
539 partition.push(perimeters[idx].take().unwrap());
540 }
541 results.push(partition);
542 }
543 // Sanity check
544 for maybe_perimeter in perimeters {
545 assert!(maybe_perimeter.is_none());
546 }
547 results
548 }
549
550 /// Assign each perimeter one of `num_colors`, such that no two adjacent perimeters share the
551 /// same color. May fail. The resulting colors are expressed as `[0, num_colors)`.
552 pub fn calculate_coloring(input: &[Perimeter], num_colors: usize) -> Option<Vec<usize>> {
553 let mut road_to_perimeters: HashMap<RoadID, Vec<usize>> = HashMap::new();
554 for (idx, perimeter) in input.iter().enumerate() {
555 for id in &perimeter.roads {
556 road_to_perimeters
557 .entry(id.road)
558 .or_insert_with(Vec::new)
559 .push(idx);
560 }
561 }
562
563 // Greedily fill out a color for each perimeter, in the same order as the input
564 let mut assigned_colors = Vec::new();
565 for (this_idx, perimeter) in input.iter().enumerate() {
566 let mut available_colors: Vec<bool> =
567 std::iter::repeat(true).take(num_colors).collect();
568 // Find all neighbors
569 for id in &perimeter.roads {
570 for other_idx in &road_to_perimeters[&id.road] {
571 // We assign colors in order, so any neighbor index smaller than us has been
572 // chosen
573 if *other_idx < this_idx {
574 available_colors[assigned_colors[*other_idx]] = false;
575 }
576 }
577 }
578 if let Some(color) = available_colors.iter().position(|x| *x) {
579 assigned_colors.push(color);
580 } else {
581 // Too few colors?
582 return None;
583 }
584 }
585 Some(assigned_colors)
586 }
587
588 pub fn to_block(self, map: &Map) -> Result<Block> {
589 // Trace along the perimeter and build the polygon
590 let mut pts: Vec<Pt2D> = Vec::new();
591 let mut first_intersection = None;
592 for pair in self.roads.windows(2) {
593 let lane1 = pair[0].get_outermost_lane(map);
594 let road1 = map.get_parent(lane1.id);
595 let lane2 = pair[1].get_outermost_lane(map);
596 // If lane1 and lane2 are the same, then it just means we found a dead-end road with
597 // exactly one lane, which is usually a footway or cycleway that legitimately is a
598 // dead-end, or connects to some other road we didn't import. We'll just trace around
599 // it like a normal dead-end road.
600 let mut pl = match pair[0].side {
601 SideOfRoad::Right => road1
602 .center_pts
603 .shift_right(road1.get_half_width())
604 // TODO Remove after fixing whatever map import error allows a bad PolyLine to
605 // wind up here at all
606 .unwrap_or_else(|err| {
607 warn!(
608 "Can't get right edge of {} ({}): {}",
609 road1.id, err, road1.orig_id
610 );
611 road1.center_pts.clone()
612 }),
613 SideOfRoad::Left => road1
614 .center_pts
615 .shift_left(road1.get_half_width())
616 .unwrap_or_else(|err| {
617 warn!(
618 "Can't get left edge of {} ({}): {}",
619 road1.id, err, road1.orig_id
620 );
621 road1.center_pts.clone()
622 }),
623 };
624 if lane1.dir == Direction::Back {
625 pl = pl.reversed();
626 }
627 let keep_lane_orientation = if pair[0].road == pair[1].road {
628 // We're doubling back at a dead-end. Always follow the orientation of the lane.
629 true
630 } else {
631 match lane1.common_endpoint(lane2) {
632 CommonEndpoint::One(i) => i == lane1.dst_i,
633 CommonEndpoint::Both => {
634 // Two different roads link the same two intersections. I don't think we
635 // can decide the order of points other than seeing which endpoint is
636 // closest to our last point.
637 if let Some(last) = pts.last() {
638 last.dist_to(pl.first_pt()) < last.dist_to(pl.last_pt())
639 } else {
640 // The orientation doesn't matter
641 true
642 }
643 }
644 CommonEndpoint::None => bail!(
645 "{} and {} don't share a common endpoint",
646 lane1.id,
647 lane2.id
648 ),
649 }
650 };
651 if !keep_lane_orientation {
652 pl = pl.reversed();
653 }
654
655 // Before we add this road's points, try to trace along the polygon's boundary. Usually
656 // this has no effect (we'll dedupe points), but sometimes there's an extra curve.
657 //
658 // Note this logic is similar to how we find SharedSidewalkCorners. Don't rely on that
659 // existing, since the outermost lane mightn't be a sidewalk.
660 //
661 // If the ring.doubles_back(), don't bother. If we tried to trace the boundary, it
662 // usually breaks the final Ring we produce. Better to skip bad intersection polygons
663 // and still produce a reasonable looking block.
664 let prev_i = if keep_lane_orientation {
665 lane1.src_i
666 } else {
667 lane1.dst_i
668 };
669 if first_intersection.is_none() {
670 first_intersection = Some(prev_i);
671 }
672 if let Some(last_pt) = pts.last() {
673 let prev_i = map.get_i(prev_i);
674 let ring = prev_i.polygon.get_outer_ring();
675 if !ring.doubles_back() {
676 // At dead-ends, trace around the intersection on the longer side
677 let longer = prev_i.is_deadend_for_driving(map);
678 if let Some(slice) = ring.get_slice_between(*last_pt, pl.first_pt(), longer) {
679 pts.extend(slice.into_points());
680 }
681 }
682 }
683
684 pts.extend(pl.into_points());
685 }
686 // Do the intersection boundary tracing for the last piece. We didn't know enough to do it
687 // the first time.
688 let first_intersection = map.get_i(first_intersection.unwrap());
689 let ring = first_intersection.polygon.get_outer_ring();
690 if !ring.doubles_back() {
691 let longer = first_intersection.is_deadend_for_driving(map);
692 if let Some(slice) = ring.get_slice_between(*pts.last().unwrap(), pts[0], longer) {
693 pts.extend(slice.into_points());
694 }
695 }
696 pts.push(pts[0]);
697 pts.dedup();
698 let polygon = Ring::unsafe_deduping_new(pts)?.into_polygon();
699 // TODO To debug anyway, we could plumb through a Tessellation, but there's pretty much
700 // always a root problem in the map geometry that should be properly fixed.
701
702 Ok(Block {
703 perimeter: self,
704 polygon,
705 })
706 }
707
708 /// Does this perimeter completely enclose the other?
709 pub fn contains(&self, other: &Perimeter) -> bool {
710 other
711 .roads
712 .iter()
713 .all(|id| self.interior.contains(&id.road) || self.roads.contains(id))
714 }
715
716 /// Shrinks or expands the perimeter by tracing the opposite side of the road.
717 pub fn flip_side_of_road(mut self) -> Self {
718 for road_side in &mut self.roads {
719 *road_side = road_side.other_side();
720 }
721 self
722 }
723
724 /// Looks for perimeters that're completely surrounded by other perimeters, aka, holes.
725 /// Attempts to merge them with the surrounding perimeter. This can be useful for applications
726 /// trying to incrementally merge adjacent blocks without creating splits, because it's often
727 /// impossible to do this in one merge when there are holes.
728 ///
729 /// This should never "lose" any of the input. It may not be fast or guaranteed to find and fix
730 /// every hole.
731 pub fn merge_holes(map: &Map, mut perims: Vec<Perimeter>) -> Vec<Perimeter> {
732 // Fixed-point for now -- find and fix one hole at a time. Slow, but simple.
733 loop {
734 let num_before = perims.len();
735
736 // Look for one hole
737 let mut hole = None;
738 for (idx, perim) in perims.iter().enumerate() {
739 let copy = perim.clone().flip_side_of_road();
740 // Now that we've "expanded" the perimeter to the other side of the road, is there
741 // another perimeter that completely contains it?
742 if let Some(surrounding) = perims.iter().position(|p| p.contains(©)) {
743 hole = Some((idx, surrounding));
744 // TODO If the first hole found doesn't merge for some reason, then we'll get
745 // stuck and just give up, even if there are other holes that might be fixed
746 // later. The indices just get too tricky.
747 break;
748 }
749 }
750 if let Some((mut idx1, mut idx2)) = hole {
751 // Merge these two
752 if idx2 < idx1 {
753 std::mem::swap(&mut idx1, &mut idx2);
754 }
755 let perim1 = perims.remove(idx2);
756 let perim2 = perims.remove(idx1);
757
758 let stepwise_debug = false;
759 perims.extend(Self::merge_all(map, vec![perim1, perim2], stepwise_debug));
760 }
761
762 if perims.len() == num_before {
763 // We didn't change anything, so stop
764 break;
765 }
766 }
767
768 perims
769 }
770}
771
772impl fmt::Debug for Perimeter {
773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774 writeln!(f, "Perimeter:")?;
775 for id in &self.roads {
776 writeln!(f, "- {:?} of {}", id.side, id.road)?;
777 }
778 Ok(())
779 }
780}