ltn/render/
cells.rs

1use std::collections::{HashSet, VecDeque};
2
3use geom::{Bounds, Distance, Polygon};
4use map_gui::tools::Grid;
5use map_model::Map;
6use widgetry::{Color, GeomBatch};
7
8use crate::render::colors;
9use crate::Neighbourhood;
10
11const RESOLUTION_M: f64 = 10.0;
12
13pub struct RenderCells {
14    /// Rarely, this might be empty if the area is very small
15    pub polygons_per_cell: Vec<Vec<Polygon>>,
16    /// Colors per cell, such that adjacent cells are colored differently
17    pub colors: Vec<Color>,
18
19    boundary_polygon: Polygon,
20}
21
22struct RenderCellsBuilder {
23    /// The grid only covers the boundary polygon of the neighbourhood. The values are cell indices,
24    /// and `Some(num_cells)` marks the boundary of the neighbourhood.
25    grid: Grid<Option<usize>>,
26    colors: Vec<Color>,
27    /// Bounds of the neighbourhood boundary polygon
28    bounds: Bounds,
29
30    boundary_polygon: Polygon,
31}
32
33impl RenderCells {
34    /// Partition a neighbourhood's boundary polygon based on the cells. This discretizes space into
35    /// a grid, and then extracts a polygon from the raster. The results don't look perfect, but
36    /// it's fast.
37    pub fn new(map: &Map, neighbourhood: &Neighbourhood) -> RenderCells {
38        RenderCellsBuilder::new(map, neighbourhood).finalize()
39    }
40
41    /// Draw cells as areas with different colors. The colors are meaningless, but the same color
42    /// won't be shared between adjacent cells.
43    pub fn draw_colored_areas(&self) -> GeomBatch {
44        let mut batch = GeomBatch::new();
45        for (color, polygons) in self.colors.iter().zip(self.polygons_per_cell.iter()) {
46            for poly in polygons {
47                batch.push(*color, poly.clone());
48            }
49        }
50        batch
51    }
52
53    /// Draw the boundary between cells as a thick outline. It's meant to look like the
54    /// neighbourhood is split into disconnected islands.
55    pub fn draw_island_outlines(&self) -> GeomBatch {
56        let neighbourhood_boundary = self
57            .boundary_polygon
58            .get_outer_ring()
59            .to_outline(Distance::meters(25.0));
60
61        let mut batch = GeomBatch::new();
62        for (cell_color, polygons) in self.colors.iter().zip(self.polygons_per_cell.iter()) {
63            for poly in polygons {
64                // If the cell is disconnected, keep drawing this as an area to point out the
65                // problem
66                if *cell_color == colors::DISCONNECTED_CELL {
67                    batch.push(*cell_color, poly.clone());
68                    continue;
69                }
70
71                let boundary = poly.get_outer_ring().to_outline(Distance::meters(5.0));
72
73                let color = cell_color.alpha(1.0).shade(0.2);
74                // If possible, try to erase where the cell boundary touches the perimeter road.
75                if let Ok(list) = boundary.difference(&neighbourhood_boundary) {
76                    batch.extend(color, list);
77                } else {
78                    batch.push(color, boundary);
79                }
80            }
81        }
82        batch
83    }
84
85    /// Per cell, convert all polygons to a `geo::MultiPolygon`. Leave the coordinate system as map-space.
86    pub fn to_multipolygons(&self) -> Vec<geo::MultiPolygon> {
87        self.polygons_per_cell
88            .clone()
89            .into_iter()
90            .map(Polygon::union_all_into_multipolygon)
91            .collect()
92    }
93}
94
95impl RenderCellsBuilder {
96    fn new(map: &Map, neighbourhood: &Neighbourhood) -> RenderCellsBuilder {
97        let boundary_polygon = neighbourhood.boundary_polygon.clone();
98        // Make a 2D grid covering the polygon. Each tile in the grid contains a cell index, which
99        // will become a color by the end. None means no cell is assigned yet.
100        let bounds = boundary_polygon.get_bounds();
101        let mut grid: Grid<Option<usize>> = Grid::new(
102            (bounds.width() / RESOLUTION_M).ceil() as usize,
103            (bounds.height() / RESOLUTION_M).ceil() as usize,
104            None,
105        );
106
107        // Initially fill out the grid based on the roads in each cell
108        let mut warn_leak = true;
109        for (cell_idx, cell) in neighbourhood.cells.iter().enumerate() {
110            for (r, interval) in &cell.roads {
111                let road = map.get_r(*r);
112                // Some roads with a filter are _very_ short, and this fails. The connecting roads
113                // on either side should contribute a grid cell and wind up fine.
114                if let Ok(slice) = road
115                    .center_pts
116                    .maybe_exact_slice(interval.start, interval.end)
117                {
118                    // Walk along the center line. We could look at the road's thickness and fill
119                    // out points based on that, but the diffusion should take care of it.
120                    for (pt, _) in
121                        slice.step_along(Distance::meters(RESOLUTION_M / 2.0), Distance::ZERO)
122                    {
123                        let grid_idx = grid.idx(
124                            ((pt.x() - bounds.min_x) / RESOLUTION_M) as usize,
125                            ((pt.y() - bounds.min_y) / RESOLUTION_M) as usize,
126                        );
127                        // Due to tunnels/bridges, sometimes a road belongs to a neighbourhood, but
128                        // leaks outside the neighbourhood's boundary. Avoid crashing. The real fix
129                        // is to better define boundaries in the face of z-order changes.
130                        //
131                        // Example is https://www.openstreetmap.org/way/87298633
132                        if grid_idx >= grid.data.len() {
133                            if warn_leak {
134                                warn!(
135                                    "{} leaks outside its neighbourhood's boundary polygon, near {}",
136                                    road.id, pt
137                                );
138                                // In some neighbourhoods, there are so many warnings that logging
139                                // causes noticeable slowdown!
140                                warn_leak = false;
141                            }
142                            continue;
143                        }
144
145                        // If roads from two different cells are close enough to clobber
146                        // originally, oh well?
147                        grid.data[grid_idx] = Some(cell_idx);
148                    }
149                }
150            }
151        }
152        // Also mark the boundary polygon, so we can prevent the diffusion from "leaking" outside
153        // the area. The grid covers the rectangular bounds of the polygon. Rather than make an
154        // enum with 3 cases, just assign a new index to mean "boundary."
155        let boundary_marker = neighbourhood.cells.len();
156        for (pt, _) in
157            geom::PolyLine::unchecked_new(boundary_polygon.get_outer_ring().clone().into_points())
158                .step_along(Distance::meters(RESOLUTION_M / 2.0), Distance::ZERO)
159        {
160            // TODO Refactor helpers to transform between map-space and the grid tiles. Possibly
161            // Grid should know about this.
162            let grid_idx = grid.idx(
163                ((pt.x() - bounds.min_x) / RESOLUTION_M) as usize,
164                ((pt.y() - bounds.min_y) / RESOLUTION_M) as usize,
165            );
166            grid.data[grid_idx] = Some(boundary_marker);
167        }
168
169        let adjacencies = diffusion(&mut grid, boundary_marker);
170        let mut cell_colors = color_cells(neighbourhood.cells.len(), adjacencies);
171
172        // Color some special cells
173        for (idx, cell) in neighbourhood.cells.iter().enumerate() {
174            if cell.is_disconnected() {
175                cell_colors[idx] = colors::DISCONNECTED_CELL;
176            }
177        }
178
179        RenderCellsBuilder {
180            grid,
181            colors: cell_colors,
182            bounds,
183
184            boundary_polygon,
185        }
186    }
187
188    fn finalize(self) -> RenderCells {
189        let mut result = RenderCells {
190            polygons_per_cell: Vec::new(),
191            colors: Vec::new(),
192            boundary_polygon: self.boundary_polygon,
193        };
194
195        for (idx, color) in self.colors.into_iter().enumerate() {
196            // contour will find where the grid is >= a threshold value. The main grid has one
197            // number per cell, so we can't directly use it -- the area >= some cell index is
198            // meaningless. Per cell, make a new grid that just has that cell.
199            let grid: Grid<f64> = Grid {
200                width: self.grid.width,
201                height: self.grid.height,
202                data: self
203                    .grid
204                    .data
205                    .iter()
206                    .map(
207                        |maybe_cell| {
208                            if maybe_cell == &Some(idx) {
209                                1.0
210                            } else {
211                                0.0
212                            }
213                        },
214                    )
215                    .collect(),
216            };
217
218            let smooth = false;
219            let contour_builder =
220                contour::ContourBuilder::new(grid.width as u32, grid.height as u32, smooth);
221            let thresholds = vec![1.0];
222
223            let mut cell_polygons = Vec::new();
224            for contour in contour_builder.contours(&grid.data, &thresholds).unwrap() {
225                let (polygons, _) = contour.into_inner();
226                for p in polygons {
227                    if let Ok(poly) = Polygon::try_from(p) {
228                        cell_polygons.push(
229                            poly.must_scale(RESOLUTION_M)
230                                .translate(self.bounds.min_x, self.bounds.min_y),
231                        );
232                    }
233                }
234            }
235
236            // Sometimes one cell "leaks" out of the neighbourhood boundary. Not sure why. But we
237            // can just clip the result.
238            let mut clipped = Vec::new();
239            for p in cell_polygons {
240                // If clipping fails, just use the original polygon.
241                if let Ok(list) = p.intersection(&result.boundary_polygon) {
242                    clipped.extend(list);
243                } else {
244                    clipped.push(p);
245                }
246            }
247
248            result.polygons_per_cell.push(clipped);
249            result.colors.push(color);
250        }
251
252        result
253    }
254}
255
256/// Returns a set of adjacent indices. The pairs are symmetric -- (x, y) and (y, x) will both be
257/// populated. Adjacency with boundary_marker doesn't count.
258fn diffusion(grid: &mut Grid<Option<usize>>, boundary_marker: usize) -> HashSet<(usize, usize)> {
259    // Grid indices to propagate
260    let mut queue: VecDeque<usize> = VecDeque::new();
261
262    // Initially seed the queue with all colored tiles
263    for (idx, value) in grid.data.iter().enumerate() {
264        if let Some(x) = value {
265            // Don't expand the boundary tiles
266            if *x != boundary_marker {
267                queue.push_back(idx);
268            }
269        }
270    }
271
272    let mut adjacencies = HashSet::new();
273
274    while !queue.is_empty() {
275        let current_idx = queue.pop_front().unwrap();
276        let current_color = grid.data[current_idx].unwrap();
277        let (current_x, current_y) = grid.xy(current_idx);
278        // Don't flood to diagonal neighbors. That would usually result in "leaking" out past the
279        // boundary tiles when the boundary polygon isn't axis-aligned.
280        // TODO But this still does "leak" out sometimes -- the cell covering 22nd/Lynn, for
281        // example.
282        for (next_x, next_y) in grid.orthogonal_neighbors(current_x, current_y) {
283            let next_idx = grid.idx(next_x, next_y);
284            if let Some(prev_color) = grid.data[next_idx] {
285                // If the color doesn't match our current_color, we've found the border between two
286                // cells.
287                if current_color != prev_color
288                    && current_color != boundary_marker
289                    && prev_color != boundary_marker
290                {
291                    adjacencies.insert((current_color, prev_color));
292                    adjacencies.insert((prev_color, current_color));
293                }
294                // If a color has been assigned, don't flood any further.
295            } else {
296                grid.data[next_idx] = Some(current_color);
297                queue.push_back(next_idx);
298            }
299        }
300    }
301
302    adjacencies
303}
304
305fn color_cells(num_cells: usize, adjacencies: HashSet<(usize, usize)>) -> Vec<Color> {
306    // This is the same greedy logic as Perimeter::calculate_coloring
307    let mut assigned_colors = Vec::new();
308    for this_idx in 0..num_cells {
309        let mut available_colors: Vec<bool> =
310            std::iter::repeat(true).take(colors::CELLS.len()).collect();
311        // Find all neighbors
312        for other_idx in 0..num_cells {
313            if adjacencies.contains(&(this_idx, other_idx)) {
314                // We assign colors in order, so any neighbor index smaller than us has been
315                // chosen
316                if other_idx < this_idx {
317                    available_colors[assigned_colors[other_idx]] = false;
318                }
319            }
320        }
321
322        // If there are multiple colors available, prefer one that hasn't been used anywhere yet.
323        // Cells far apart shouldn't seem related to the user.
324        let mut choice = None;
325        let mut backup = None;
326        for (idx, available) in available_colors.into_iter().enumerate() {
327            if !available {
328                continue;
329            }
330            if assigned_colors.iter().any(|x| *x == idx) {
331                if backup.is_none() {
332                    backup = Some(idx);
333                }
334            } else {
335                choice = Some(idx);
336                break;
337            }
338        }
339        assigned_colors.push(
340            choice
341                .or(backup)
342                .unwrap_or_else(|| assigned_colors.len() % colors::CELLS.len()),
343        );
344    }
345    assigned_colors
346        .into_iter()
347        .map(|idx| colors::CELLS[idx].alpha(0.8))
348        .collect()
349}