ltn/render/
mod.rs

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    // TODO Toggle3Zoomed could be nicer; these're not terribly visible from afar
36    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        // Draw dashed outlines surrounding the road
61        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        // TODO Also interpret lane-level? Maybe just check all the generated turns and see what's
86        // allowed / banned in practice?
87
88        // Count the number of turn restrictions at each end of the road
89        let mut icon_counter = HashMap::from([(r1.dst_i, 1), (r1.src_i, 1)]);
90
91        for (restriction, r2) in &r1.turn_restrictions {
92            // TODO "Invert" OnlyAllowTurns so we can just draw banned things
93            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                // add to the counter
97                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            // TODO Show the 'via'? Or just draw the entire shape?
105            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    // Which icon do we want?
126    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    // TODO - what should we do with these?
131    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    // Draw the svg icon
144    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
153/// Depending on the canvas zoom level, draws one of 2 things.
154// TODO Rethink filter styles and do something better than this.
155pub 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}