1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::collections::BTreeMap;

use abstutil::Timer;
use map_model::osm::RoadRank;
use map_model::{Block, Perimeter};
use widgetry::Color;

use crate::app::App;

const COLORS: [Color; 6] = [
    Color::BLUE,
    Color::YELLOW,
    Color::GREEN,
    Color::PURPLE,
    Color::PINK,
    Color::ORANGE,
];

/// An opaque ID, won't be contiguous as we adjust boundaries
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NeighborhoodID(usize);
// Some states want this
impl widgetry::mapspace::ObjectID for NeighborhoodID {}

#[derive(Clone)]
pub struct Partitioning {
    pub neighborhoods: BTreeMap<NeighborhoodID, (Block, Color)>,
    // The single / unmerged blocks never change
    pub single_blocks: Vec<Block>,
}

impl Partitioning {
    /// Only valid before the LTN tool has been activated this session
    pub fn empty() -> Partitioning {
        Partitioning {
            neighborhoods: BTreeMap::new(),
            single_blocks: Vec::new(),
        }
    }

    pub fn seed_using_heuristics(app: &App, timer: &mut Timer) -> Partitioning {
        let map = &app.primary.map;
        timer.start("find single blocks");
        let mut single_blocks = Vec::new();
        let mut single_block_perims = Vec::new();
        for perim in Perimeter::find_all_single_blocks(map) {
            if let Ok(block) = perim.to_block(map) {
                single_block_perims.push(block.perimeter.clone());
                single_blocks.push(block);
            }
        }
        timer.stop("find single blocks");

        timer.start("partition");
        let partitions = Perimeter::partition_by_predicate(single_block_perims, |r| {
            // "Interior" roads of a neighborhood aren't classified as arterial
            map.get_r(r).get_rank() == RoadRank::Local
        });

        let mut merged = Vec::new();
        for perimeters in partitions {
            // If we got more than one result back, merging partially failed. Oh well?
            merged.extend(Perimeter::merge_all(perimeters, false));
        }
        timer.stop("partition");

        timer.start_iter("blockify", merged.len());
        let mut blocks = Vec::new();
        for perimeter in merged {
            timer.next();
            match perimeter.to_block(map) {
                Ok(block) => {
                    blocks.push(block);
                }
                Err(err) => {
                    warn!("Failed to make a block from a merged perimeter: {}", err);
                }
            }
        }

        let mut neighborhoods = BTreeMap::new();
        for block in blocks {
            neighborhoods.insert(NeighborhoodID(neighborhoods.len()), (block, Color::RED));
        }
        let mut p = Partitioning {
            neighborhoods,
            single_blocks,
        };
        p.recalculate_coloring();
        p
    }

    /// True if the coloring changed
    pub fn recalculate_coloring(&mut self) -> bool {
        let perims: Vec<Perimeter> = self
            .neighborhoods
            .values()
            .map(|pair| pair.0.perimeter.clone())
            .collect();
        let colors = Perimeter::calculate_coloring(&perims, COLORS.len())
            .unwrap_or_else(|| (0..perims.len()).collect());
        let orig_coloring: Vec<Color> = self.neighborhoods.values().map(|pair| pair.1).collect();
        for (pair, color_idx) in self.neighborhoods.values_mut().zip(colors.into_iter()) {
            pair.1 = COLORS[color_idx % COLORS.len()];
        }
        let new_coloring: Vec<Color> = self.neighborhoods.values().map(|pair| pair.1).collect();
        orig_coloring != new_coloring
    }

    pub fn neighborhood_containing(&self, find_block: &Block) -> Option<NeighborhoodID> {
        // TODO We could probably build this mapping up when we do Perimeter::merge_all
        for (id, (block, _)) in &self.neighborhoods {
            if block.perimeter.contains(&find_block.perimeter) {
                return Some(*id);
            }
        }
        None
    }
}