ltn/logic/
partition.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5
6use abstio::MapName;
7use abstutil::Timer;
8use blockfinding::{Block, Perimeter};
9use geom::Polygon;
10use map_model::osm::RoadRank;
11use map_model::{IntersectionID, Map, RoadID, RoadSideID};
12
13/// An opaque ID, won't be contiguous as we adjust boundaries
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15pub struct NeighbourhoodID(pub usize);
16
17/// Identifies a single / unmerged block, which never changes
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
19pub struct BlockID(usize);
20
21// Some states want this
22impl widgetry::mapspace::ObjectID for NeighbourhoodID {}
23impl widgetry::mapspace::ObjectID for BlockID {}
24
25#[derive(Clone, Serialize, Deserialize)]
26pub struct Partitioning {
27    pub map: MapName,
28    neighbourhoods: BTreeMap<NeighbourhoodID, NeighbourhoodInfo>,
29    // The single / unmerged blocks never change
30    single_blocks: Vec<Block>,
31
32    neighbourhood_id_counter: usize,
33
34    // Invariant: This is a surjection, every block belongs to exactly one neighbourhood
35    block_to_neighbourhood: BTreeMap<BlockID, NeighbourhoodID>,
36
37    // TODO Possibly this never happens anymore and can go away
38    pub broken: bool,
39
40    #[serde(skip_serializing, skip_deserializing)]
41    pub custom_boundaries: BTreeMap<NeighbourhoodID, CustomBoundary>,
42}
43
44#[derive(Clone, Serialize, Deserialize)]
45pub struct NeighbourhoodInfo {
46    pub block: Block,
47    /// Draw a special cone of light when focused on this neighbourhood. It doesn't change which
48    /// roads can be edited.
49    pub override_drawing_boundary: Option<Polygon>,
50}
51
52impl NeighbourhoodInfo {
53    fn new(block: Block) -> Self {
54        Self {
55            block,
56            override_drawing_boundary: None,
57        }
58    }
59}
60
61#[derive(Clone)]
62pub struct CustomBoundary {
63    pub name: String,
64    pub boundary_polygon: Polygon,
65    pub borders: BTreeSet<IntersectionID>,
66    pub interior_roads: BTreeSet<RoadID>,
67}
68
69impl Partitioning {
70    /// Only valid before the LTN tool has been activated this session
71    pub fn empty() -> Partitioning {
72        Partitioning {
73            map: MapName::new("zz", "temp", "orary"),
74            neighbourhoods: BTreeMap::new(),
75            single_blocks: Vec::new(),
76
77            neighbourhood_id_counter: 0,
78
79            block_to_neighbourhood: BTreeMap::new(),
80
81            broken: false,
82            custom_boundaries: BTreeMap::new(),
83        }
84    }
85
86    pub fn is_empty(&self) -> bool {
87        self.neighbourhoods.is_empty()
88    }
89
90    pub fn seed_using_heuristics(map: &Map, timer: &mut Timer) -> Partitioning {
91        timer.start("seed partitioning with heuristics");
92
93        timer.start("find single blocks");
94        let input_single_blocks = Perimeter::find_all_single_blocks(map);
95        timer.stop("find single blocks");
96
97        // Merge holes upfront. Otherwise, it's usually impossible to expand a boundary with a
98        // block containing a hole. Plus, there's no known scenario when somebody would want to
99        // make a neighbourhood boundary involve a hole.
100        timer.start("merge holes");
101        let input = Perimeter::merge_holes(map, input_single_blocks);
102        timer.stop("merge holes");
103
104        let mut single_blocks = Vec::new();
105        let mut single_block_perims = Vec::new();
106        timer.start_iter("fix deadends and blockify", input.len());
107        for mut perim in input {
108            timer.next();
109            // TODO Some perimeters don't blockify after collapsing dead-ends. So do this
110            // upfront, and separately work on any blocks that don't show up.
111            // https://github.com/a-b-street/abstreet/issues/841
112            perim.collapse_deadends();
113            if let Ok(block) = perim.to_block(map) {
114                single_block_perims.push(block.perimeter.clone());
115                single_blocks.push(block);
116            }
117        }
118
119        timer.start("partition");
120        let partitions = Perimeter::partition_by_predicate(single_block_perims, |r| {
121            // "Interior" roads of a neighbourhood aren't classified as arterial
122            map.get_r(r).get_rank() == RoadRank::Local
123        });
124
125        let mut merged = Vec::new();
126        for perimeters in partitions {
127            // If we got more than one result back, merging partially failed. Oh well?
128            let stepwise_debug = false;
129            merged.extend(Perimeter::merge_all(map, perimeters, stepwise_debug));
130        }
131        timer.stop("partition");
132
133        timer.start_iter("blockify merged", merged.len());
134        let mut blocks = Vec::new();
135        for perimeter in merged {
136            timer.next();
137            match perimeter.to_block(map) {
138                Ok(block) => {
139                    blocks.push(block);
140                }
141                Err(err) => {
142                    warn!("Failed to make a block from a merged perimeter: {}", err);
143                }
144            }
145        }
146
147        let mut neighbourhoods = BTreeMap::new();
148        for block in blocks {
149            neighbourhoods.insert(
150                NeighbourhoodID(neighbourhoods.len()),
151                NeighbourhoodInfo::new(block),
152            );
153        }
154        let neighbourhood_id_counter = neighbourhoods.len();
155        let mut p = Partitioning {
156            map: map.get_name().clone(),
157            neighbourhoods,
158            single_blocks,
159
160            neighbourhood_id_counter,
161            block_to_neighbourhood: BTreeMap::new(),
162            broken: false,
163            custom_boundaries: BTreeMap::new(),
164        };
165
166        // TODO We could probably build this up as we go
167        for id in p.all_block_ids() {
168            if let Some(neighbourhood) = p.neighbourhood_containing(id) {
169                p.block_to_neighbourhood.insert(id, neighbourhood);
170            } else {
171                error!(
172                        "Block doesn't belong to any neighbourhood. Continuing without boundary adjustment. {:?}",
173                        p.get_block(id).perimeter
174                    );
175                p.broken = true;
176            }
177        }
178
179        timer.stop("seed partitioning with heuristics");
180        p
181    }
182
183    /// Add all specified blocks to new_owner. `Ok(None)` is success.  `Ok(Some(x))` is also
184    /// success, but means the old neighbourhood of SOME block in `add_all` is now gone and
185    /// replaced with something new. (This call shouldn't be used to remove multiple blocks at
186    /// once, since interpreting the result is confusing!)
187    pub fn transfer_blocks(
188        &mut self,
189        map: &Map,
190        add_all: Vec<BlockID>,
191        new_owner: NeighbourhoodID,
192    ) -> Result<Option<NeighbourhoodID>> {
193        // Is the newly expanded neighbourhood a valid perimeter?
194        let mut new_owner_blocks = self.neighbourhood_to_blocks(new_owner);
195        new_owner_blocks.extend(add_all.clone());
196        let mut new_neighbourhood_blocks = self.make_merged_blocks(map, new_owner_blocks)?;
197        if new_neighbourhood_blocks.len() != 1 {
198            // This happens when a hole would be created by adding this block. There are probably
199            // some smaller blocks nearby to add first.
200            bail!("Couldn't add block -- you may need to add an intermediate block first to avoid a hole, or there's a bug you can't workaround yet. Try adding pink blocks first.");
201        }
202        let new_neighbourhood_block = new_neighbourhood_blocks.pop().unwrap();
203
204        let old_owners: BTreeSet<NeighbourhoodID> = add_all
205            .iter()
206            .map(|block| self.block_to_neighbourhood[block])
207            .collect();
208        // Are each of the old neighbourhoods, minus any new blocks, still valid?
209        let mut return_value = None;
210        for old_owner in old_owners {
211            let mut old_owner_blocks = self.neighbourhood_to_blocks(old_owner);
212            for x in &add_all {
213                old_owner_blocks.remove(x);
214            }
215            if old_owner_blocks.is_empty() {
216                self.neighbourhoods.remove(&old_owner).unwrap();
217                return_value = Some(new_owner);
218                continue;
219            }
220
221            let mut old_neighbourhood_blocks =
222                self.make_merged_blocks(map, old_owner_blocks.clone())?;
223            // We might be splitting the old neighbourhood into multiple pieces! Pick the largest piece
224            // as the old_owner (so the UI for trimming a neighbourhood is less jarring), and create new
225            // neighbourhoods for the others.
226            old_neighbourhood_blocks.sort_by_key(|block| block.perimeter.interior.len());
227            self.neighbourhoods.get_mut(&old_owner).unwrap().block =
228                old_neighbourhood_blocks.pop().unwrap();
229            let new_splits = !old_neighbourhood_blocks.is_empty();
230            for split_piece in old_neighbourhood_blocks {
231                let new_neighbourhood = NeighbourhoodID(self.neighbourhood_id_counter);
232                self.neighbourhood_id_counter += 1;
233                self.neighbourhoods
234                    .insert(new_neighbourhood, NeighbourhoodInfo::new(split_piece));
235            }
236            if new_splits {
237                // We need to update the owner of all single blocks in these new pieces
238                for id in old_owner_blocks {
239                    self.block_to_neighbourhood
240                        .insert(id, self.neighbourhood_containing(id).unwrap());
241                }
242            }
243        }
244
245        // Set up the newly expanded neighbourhood
246        self.neighbourhoods.get_mut(&new_owner).unwrap().block = new_neighbourhood_block;
247        for id in add_all {
248            self.block_to_neighbourhood.insert(id, new_owner);
249        }
250        Ok(return_value)
251    }
252
253    /// Needs to find an existing neighbourhood to take the block, or make a new one
254    pub fn remove_block_from_neighbourhood(
255        &mut self,
256        map: &Map,
257        id: BlockID,
258    ) -> Result<Option<NeighbourhoodID>> {
259        let old_owner = self.block_to_neighbourhood(id);
260        // Find all RoadSideIDs in the block matching the current neighbourhood perimeter. Look for
261        // the first one that borders another neighbourhood, and transfer the block there.
262        // TODO This can get unintuitive -- if we remove a block bordering two other
263        // neighbourhoods, which one should we donate to?
264        let current_perim_set: BTreeSet<RoadSideID> = self.neighbourhoods[&old_owner]
265            .block
266            .perimeter
267            .roads
268            .iter()
269            .cloned()
270            .collect();
271        for road_side in &self.get_block(id).perimeter.roads {
272            if !current_perim_set.contains(road_side) {
273                continue;
274            }
275            // Is there another neighbourhood that has the other side of this road on its perimeter?
276            // TODO We could map road -> BlockID then use block_to_neighbourhood
277            let other_side = road_side.other_side();
278            if let Some((new_owner, _)) = self
279                .neighbourhoods
280                .iter()
281                .find(|(_, info)| info.block.perimeter.roads.contains(&other_side))
282            {
283                return self.transfer_blocks(map, vec![id], *new_owner);
284            }
285        }
286
287        // We didn't find any match, so we're jettisoning a block near the edge of the map (or a
288        // buggy area missing blocks). Create a new neighbourhood with just this block.
289        let new_owner = NeighbourhoodID(self.neighbourhood_id_counter);
290        self.neighbourhood_id_counter += 1;
291        self.neighbourhoods.insert(
292            new_owner,
293            NeighbourhoodInfo::new(self.get_block(id).clone()),
294        );
295        let result = self.transfer_blocks(map, vec![id], new_owner);
296        if result.is_err() {
297            // Revert the change above!
298            self.neighbourhoods.remove(&new_owner).unwrap();
299        }
300        result
301    }
302}
303
304// Read-only
305impl Partitioning {
306    pub fn neighbourhood_block(&self, id: NeighbourhoodID) -> &Block {
307        &self.neighbourhoods[&id].block
308    }
309
310    pub fn neighbourhood_area_km2(&self, id: NeighbourhoodID) -> String {
311        // TODO Could consider using the boundary_polygon calculated by Neighbourhood
312        let area = if let Some(ref custom) = self.custom_boundaries.get(&id) {
313            custom.boundary_polygon.area()
314        } else {
315            self.neighbourhood_block(id).polygon.area()
316        };
317
318        // Convert from m^2 to km^2
319        let area = area / 1_000_000.0;
320        format!("~{:.1} km²", area)
321    }
322
323    pub fn get_info(&self, id: NeighbourhoodID) -> &NeighbourhoodInfo {
324        &self.neighbourhoods[&id]
325    }
326
327    pub fn override_neighbourhood_boundary_polygon(
328        &mut self,
329        id: NeighbourhoodID,
330        polygon: Polygon,
331    ) {
332        self.neighbourhoods
333            .get_mut(&id)
334            .unwrap()
335            .override_drawing_boundary = Some(polygon);
336    }
337
338    pub fn add_custom_boundary(&mut self, custom: CustomBoundary) -> NeighbourhoodID {
339        let id = NeighbourhoodID(self.neighbourhood_id_counter);
340        self.neighbourhood_id_counter += 1;
341        self.custom_boundaries.insert(id, custom);
342        id
343    }
344
345    pub fn all_neighbourhoods(&self) -> &BTreeMap<NeighbourhoodID, NeighbourhoodInfo> {
346        &self.neighbourhoods
347    }
348
349    // Just used for initial creation
350    fn neighbourhood_containing(&self, find_block: BlockID) -> Option<NeighbourhoodID> {
351        // TODO We could probably build this mapping up when we do Perimeter::merge_all
352        let find_block = self.get_block(find_block);
353        for (id, info) in &self.neighbourhoods {
354            if info.block.perimeter.contains(&find_block.perimeter) {
355                return Some(*id);
356            }
357        }
358        None
359    }
360
361    pub fn all_single_blocks(&self) -> Vec<(BlockID, &Block)> {
362        self.single_blocks
363            .iter()
364            .enumerate()
365            .map(|(idx, block)| (BlockID(idx), block))
366            .collect()
367    }
368
369    pub fn all_block_ids(&self) -> Vec<BlockID> {
370        (0..self.single_blocks.len()).map(BlockID).collect()
371    }
372
373    pub fn get_block(&self, id: BlockID) -> &Block {
374        &self.single_blocks[id.0]
375    }
376
377    pub fn block_to_neighbourhood(&self, id: BlockID) -> NeighbourhoodID {
378        self.block_to_neighbourhood[&id]
379    }
380
381    pub fn neighbourhood_to_blocks(&self, id: NeighbourhoodID) -> BTreeSet<BlockID> {
382        let mut result = BTreeSet::new();
383        for (block, n) in &self.block_to_neighbourhood {
384            if *n == id {
385                result.insert(*block);
386            }
387        }
388        result
389    }
390
391    pub fn some_block_in_neighbourhood(&self, id: NeighbourhoodID) -> BlockID {
392        for (block, neighbourhood) in &self.block_to_neighbourhood {
393            if id == *neighbourhood {
394                return *block;
395            }
396        }
397        unreachable!("{:?} has no blocks", id);
398    }
399
400    /// Blocks on the "frontier" are adjacent to the perimeter, either just inside or outside.
401    pub fn calculate_frontier(&self, perim: &Perimeter) -> BTreeSet<BlockID> {
402        let perim_roads: BTreeSet<RoadID> = perim.roads.iter().map(|id| id.road).collect();
403
404        let mut frontier = BTreeSet::new();
405        for (block_id, block) in self.all_single_blocks() {
406            for road_side_id in &block.perimeter.roads {
407                // If the perimeter has this RoadSideID on the same side, we're just inside. If it has
408                // the other side, just on the outside. Either way, on the frontier.
409                if perim_roads.contains(&road_side_id.road) {
410                    frontier.insert(block_id);
411                    break;
412                }
413            }
414        }
415        frontier
416    }
417
418    fn adjacent_blocks(&self, id: BlockID) -> BTreeSet<BlockID> {
419        let mut blocks = self.calculate_frontier(&self.get_block(id).perimeter);
420        blocks.retain(|x| *x != id);
421        blocks
422    }
423
424    // Possibly returns multiple merged blocks. The input is never "lost" -- if any perimeter fails
425    // to become a block, fail the whole operation.
426    fn make_merged_blocks(&self, map: &Map, input: BTreeSet<BlockID>) -> Result<Vec<Block>> {
427        let mut perimeters = Vec::new();
428        for id in input {
429            perimeters.push(self.get_block(id).perimeter.clone());
430        }
431        let mut blocks = Vec::new();
432        let stepwise_debug = false;
433        for perim in Perimeter::merge_all(map, perimeters, stepwise_debug) {
434            blocks.push(perim.to_block(map)?);
435        }
436        Ok(blocks)
437    }
438
439    /// We want to add target_block to new_owner, but we can't. Find the blocks we may need to add
440    /// first.
441    pub fn find_intermediate_blocks(
442        &self,
443        new_owner: NeighbourhoodID,
444        target_block: BlockID,
445    ) -> Vec<BlockID> {
446        let adjacent_to_target_block = self.adjacent_blocks(target_block);
447        let _new_owner_frontier =
448            self.calculate_frontier(&self.neighbourhood_block(new_owner).perimeter);
449
450        let mut result = Vec::new();
451        for id in adjacent_to_target_block.clone() {
452            // A block already part of new_owner never makes sense
453            if self.block_to_neighbourhood[&id] == new_owner {
454                continue;
455            }
456
457            // TODO: intersect the two above -- aka, look for blocks adjacent both to target_block
458            // and new_owner. But this seems too eager and maybe covered by the below condition.
459            /*if self.block_to_neighbourhood(id) != new_owner && new_owner_frontier.contains(&id) {
460                result.push(id);
461                continue;
462            }*/
463
464            // Look for holes, totally surrounded by other holes or target_block.
465            if self
466                .adjacent_blocks(id)
467                .into_iter()
468                .all(|x| x == target_block || adjacent_to_target_block.contains(&x))
469            {
470                result.push(id);
471            }
472        }
473
474        result
475    }
476}