map_gui/tools/
draw_overlapping_paths.rs

1use std::collections::BTreeMap;
2
3use geom::{Distance, Pt2D, Ring};
4use map_model::{CommonEndpoint, Direction, PathStepV2, PathV2, RoadID};
5use widgetry::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
6use widgetry::Color;
7
8use crate::AppLike;
9
10pub fn draw_overlapping_paths(
11    app: &dyn AppLike,
12    paths: Vec<(PathV2, Color)>,
13) -> ToggleZoomedBuilder {
14    // Per road, just figure out what colors we need and whether to use the full road or start/end
15    // mid-way through
16    let mut colors_per_road: BTreeMap<RoadID, Vec<(Color, Option<DistanceInterval>)>> =
17        BTreeMap::new();
18    let mut colors_per_movement: Vec<(RoadID, RoadID, Color)> = Vec::new();
19    for (path, color) in paths {
20        for (idx, step) in path.get_steps().iter().enumerate() {
21            match step {
22                PathStepV2::Along(dr) | PathStepV2::Contraflow(dr) => {
23                    let road_len = app.map().get_r(dr.road).length();
24                    // TODO Handle Contraflow. Doesn't it just invert the direction we check?
25                    let interval = if idx == 0 {
26                        if dr.dir == Direction::Fwd {
27                            Some(DistanceInterval {
28                                start: path.get_req().start.dist_along(),
29                                end: road_len,
30                            })
31                        } else {
32                            Some(DistanceInterval {
33                                start: Distance::ZERO,
34                                // TODO I'm not sure why this is necessary, or if it's always
35                                // correct. In one case where req.start comes from alt_start on the
36                                // opposite side of the road, it's needed -- maybe meaning
37                                // equiv_pos is broken.
38                                end: road_len - path.get_req().start.dist_along(),
39                            })
40                        }
41                    } else if idx == path.get_steps().len() - 1 {
42                        if dr.dir == Direction::Fwd {
43                            Some(DistanceInterval {
44                                start: Distance::ZERO,
45                                end: path.get_req().end.dist_along(),
46                            })
47                        } else {
48                            Some(DistanceInterval {
49                                // TODO Same as above -- this works, but I don't know why.
50                                start: road_len - path.get_req().end.dist_along(),
51                                end: road_len,
52                            })
53                        }
54                    } else {
55                        None
56                    };
57                    colors_per_road
58                        .entry(dr.road)
59                        .or_insert_with(Vec::new)
60                        .push((color, interval));
61                }
62                PathStepV2::Movement(m) => {
63                    colors_per_movement.push((m.from.road, m.to.road, color));
64                }
65                PathStepV2::ContraflowMovement(m) => {
66                    colors_per_movement.push((m.to.road, m.from.road, color));
67                }
68            }
69        }
70    }
71
72    // Per road and color, mark the 4 corners of the thickened polyline.
73    // (beginning left, beginning right, end left, end right)
74    // TODO Make Color implement Ord; use hex in the meantime
75    let mut pieces: BTreeMap<(RoadID, String), (Pt2D, Pt2D, Pt2D, Pt2D)> = BTreeMap::new();
76    // Per road, divide the needed colors proportionally
77    let mut draw = ToggleZoomed::builder();
78    for (road, colors) in colors_per_road {
79        let road = app.map().get_r(road);
80        let width_per_piece = road.get_width() / (colors.len() as f64);
81        for (idx, (color, interval)) in colors.into_iter().enumerate() {
82            // Don't directly use road.shift_from_left_side, since we maybe need to clip
83            let center_line = if let Some(interval) = interval {
84                road.center_pts
85                    .maybe_exact_slice(interval.start, interval.end)
86            } else {
87                Ok(road.center_pts.clone())
88            };
89            if let Ok(pl) = center_line.and_then(|pl| {
90                pl.shift_from_center(road.get_width(), (0.5 + (idx as f64)) * width_per_piece)
91            }) {
92                let polygon = pl.make_polygons(width_per_piece);
93                draw.unzoomed.push(color.alpha(0.8), polygon.clone());
94                draw.zoomed.push(color.alpha(0.5), polygon);
95
96                // Reproduce what make_polygons does to get the 4 corners
97                if let Some(corners) = pl.get_four_corners_of_thickened(width_per_piece) {
98                    pieces.insert((road.id, color.as_hex()), corners);
99                }
100            }
101        }
102    }
103
104    // Fill in intersections
105    for (from, to, color) in colors_per_movement {
106        if let Some(from_corners) = pieces.get(&(from, color.as_hex())) {
107            if let Some(to_corners) = pieces.get(&(to, color.as_hex())) {
108                let from_road = app.map().get_r(from);
109                let to_road = app.map().get_r(to);
110                if let CommonEndpoint::One(i) = from_road.common_endpoint(to_road) {
111                    let (from_left, from_right) = if from_road.src_i == i {
112                        (from_corners.0, from_corners.1)
113                    } else {
114                        (from_corners.2, from_corners.3)
115                    };
116                    let (to_left, to_right) = if to_road.src_i == i {
117                        (to_corners.0, to_corners.1)
118                    } else {
119                        (to_corners.2, to_corners.3)
120                    };
121                    // Glue the 4 corners together
122                    if let Ok(ring) =
123                        Ring::new(vec![from_left, from_right, to_right, to_left, from_left])
124                    {
125                        let polygon = ring.into_polygon();
126                        draw.unzoomed.push(color.alpha(0.8), polygon.clone());
127                        draw.zoomed.push(color.alpha(0.5), polygon);
128                    }
129                }
130            }
131        }
132    }
133
134    draw
135}
136
137struct DistanceInterval {
138    start: Distance,
139    end: Distance,
140}