1mod cells;
2pub mod colors;
3mod filters;
4
5use std::collections::HashMap;
6
7use geom::{Angle, Distance, Pt2D};
8use map_model::{AmenityType, ExtraPOIType, FilterType, Map, RestrictionType, Road, TurnType};
9use widgetry::mapspace::DrawCustomUnzoomedShapes;
10use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, RewriteColor, Text};
11
12pub use cells::RenderCells;
13pub use filters::render_modal_filters;
14
15pub fn render_poi_icons(ctx: &EventCtx, map: &Map) -> Drawable {
16 let mut batch = GeomBatch::new();
17 let school = GeomBatch::load_svg(ctx, "system/assets/map/school.svg")
18 .scale(0.2)
19 .color(RewriteColor::ChangeAll(Color::WHITE));
20
21 for b in map.all_buildings() {
22 if b.amenities.iter().any(|a| {
23 let at = AmenityType::categorize(&a.amenity_type);
24 at == Some(AmenityType::School) || at == Some(AmenityType::University)
25 }) {
26 batch.append(school.clone().centered_on(b.polygon.polylabel()));
27 }
28 }
29
30 let tfl =
31 GeomBatch::load_svg(ctx, "system/assets/map/tfl_underground.svg").scale_to_fit_width(20.0);
32 let national_rail =
33 GeomBatch::load_svg(ctx, "system/assets/map/national_rail.svg").scale_to_fit_width(20.0);
34
35 for extra in map.all_extra_pois() {
37 let (name, icon) = match extra.kind {
38 ExtraPOIType::LondonUndergroundStation(ref name) => (name, &tfl),
39 ExtraPOIType::NationalRailStation(ref name) => (name, &national_rail),
40 };
41 batch.append(icon.clone().centered_on(extra.pt));
42 batch.append(
43 Text::from(Line(name).fg(Color::WHITE))
44 .bg(Color::hex("#0019A8"))
45 .render_autocropped(ctx)
46 .scale_to_fit_height(10.0)
47 .centered_on(extra.pt.offset(0.0, icon.get_bounds().height())),
48 );
49 }
50
51 ctx.upload(batch)
52}
53
54pub fn render_bus_routes(ctx: &EventCtx, map: &Map) -> Drawable {
55 let mut batch = GeomBatch::new();
56 for r in map.all_roads() {
57 if map.get_bus_routes_on_road(r.id).is_empty() {
58 continue;
59 }
60 let width = r.get_width();
62 for pl in [
63 r.center_pts.shift_left(width * 0.7),
64 r.center_pts.shift_right(width * 0.7),
65 ]
66 .into_iter()
67 .flatten()
68 {
69 batch.extend(
70 *colors::BUS_ROUTE,
71 pl.exact_dashed_polygons(
72 Distance::meters(2.0),
73 Distance::meters(5.0),
74 Distance::meters(2.0),
75 ),
76 );
77 }
78 }
79 ctx.upload(batch)
80}
81
82pub fn render_turn_restrictions(ctx: &EventCtx, map: &Map) -> Drawable {
83 let mut batch = GeomBatch::new();
84 for r1 in map.all_roads() {
85 let mut icon_counter = HashMap::from([(r1.dst_i, 1), (r1.src_i, 1)]);
90
91 for (restriction, r2) in &r1.turn_restrictions {
92 if *restriction == RestrictionType::BanTurns {
94 let (t_type, sign_pt, r1_angle, i) =
95 map.get_ban_turn_info(r1, map.get_r(*r2), &icon_counter);
96 icon_counter.entry(i).and_modify(|n| *n += 1);
98 batch.append(draw_turn_restriction_icon(
99 ctx, t_type, sign_pt, r1, r1_angle,
100 ));
101 }
102 }
103 for (_via, r2) in &r1.complicated_turn_restrictions {
104 let (t_type, sign_pt, r1_angle, i) =
106 map.get_ban_turn_info(r1, map.get_r(*r2), &icon_counter);
107 icon_counter.entry(i).and_modify(|n| *n += 1);
108 batch.append(draw_turn_restriction_icon(
109 ctx, t_type, sign_pt, r1, r1_angle,
110 ));
111 }
112 }
113 ctx.upload(batch)
114}
115
116fn draw_turn_restriction_icon(
117 ctx: &EventCtx,
118 t_type: TurnType,
119 sign_pt: Pt2D,
120 r1: &Road,
121 r1_angle: Angle,
122) -> GeomBatch {
123 let mut batch = GeomBatch::new();
124
125 let no_right_t = "system/assets/map/no_right_turn.svg";
127 let no_left_t = "system/assets/map/no_left_turn.svg";
128 let no_u_t = "system/assets/map/no_u_turn_left_to_right.svg";
129 let no_straight = "system/assets/map/no_straight_ahead.svg";
130 let other_t = "system/assets/map/thought_bubble.svg";
132
133 let icon_path = match t_type {
134 TurnType::Right => no_right_t,
135 TurnType::Left => no_left_t,
136 TurnType::UTurn => no_u_t,
137 TurnType::Crosswalk => other_t,
138 TurnType::SharedSidewalkCorner => other_t,
139 TurnType::Straight => no_straight,
140 TurnType::UnmarkedCrossing => other_t,
141 };
142
143 let icon = GeomBatch::load_svg(ctx, icon_path)
145 .scale_to_fit_width(r1.get_width().inner_meters())
146 .centered_on(sign_pt)
147 .rotate_around_batch_center(r1_angle.rotate_degs(90.0));
148
149 batch.append(icon);
150 batch
151}
152
153pub struct Toggle3Zoomed {
156 draw_zoomed: Drawable,
157 unzoomed: DrawCustomUnzoomedShapes,
158}
159
160impl Toggle3Zoomed {
161 pub fn new(draw_zoomed: Drawable, unzoomed: DrawCustomUnzoomedShapes) -> Self {
162 Self {
163 draw_zoomed,
164 unzoomed,
165 }
166 }
167
168 pub fn empty(ctx: &EventCtx) -> Self {
169 Self::new(Drawable::empty(ctx), DrawCustomUnzoomedShapes::empty())
170 }
171
172 pub fn draw(&self, g: &mut GfxCtx) {
173 if !self.unzoomed.maybe_draw(g) {
174 self.draw_zoomed.draw(g);
175 }
176 }
177}
178
179pub fn filter_svg_path(ft: FilterType) -> &'static str {
180 match ft {
181 FilterType::NoEntry => "system/assets/tools/no_entry.svg",
182 FilterType::WalkCycleOnly => "system/assets/tools/modal_filter.svg",
183 FilterType::BusGate => "system/assets/tools/bus_gate.svg",
184 FilterType::SchoolStreet => "system/assets/tools/school_street.svg",
185 }
186}
187
188pub fn filter_hide_color(ft: FilterType) -> Color {
189 match ft {
190 FilterType::WalkCycleOnly => Color::hex("#0b793a"),
191 FilterType::NoEntry => Color::RED,
192 FilterType::BusGate => *colors::BUS_ROUTE,
193 FilterType::SchoolStreet => Color::hex("#e31017"),
194 }
195}