ltn/pages/
cycle_network.rs

1use std::collections::HashMap;
2
3use map_model::LaneType;
4use widgetry::tools::PopupMsg;
5use widgetry::{Drawable, EventCtx, GeomBatch, GfxCtx, Outcome, Panel, State, TextExt, Widget};
6
7use crate::components::{AppwidePanel, BottomPanel, Mode};
8use crate::render::colors;
9use crate::{App, Neighbourhood, Transition};
10
11pub struct CycleNetwork {
12    appwide_panel: AppwidePanel,
13    bottom_panel: Panel,
14    draw_network: Drawable,
15}
16
17impl CycleNetwork {
18    pub fn new_state(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
19        let appwide_panel = AppwidePanel::new(ctx, app, Mode::CycleNetwork);
20        let bottom_panel = BottomPanel::new(
21            ctx,
22            &appwide_panel,
23            Widget::row(vec![
24                ctx.style().btn_outline.text("Experimental!").build_def(ctx),
25                "Quietways through neighbourhoods can complement cycle lanes on main roads"
26                    .text_widget(ctx)
27                    .centered_vert(),
28            ]),
29        );
30        app.session
31            .layers
32            .show_panel(ctx, &app.cs, Some(&bottom_panel));
33        let draw_network = draw_network(ctx, app);
34
35        Box::new(Self {
36            appwide_panel,
37            bottom_panel,
38            draw_network,
39        })
40    }
41}
42
43impl State<App> for CycleNetwork {
44    fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
45        if let Some(t) =
46            self.appwide_panel
47                .event(ctx, app, &crate::save::PreserveState::CycleNetwork, help)
48        {
49            return t;
50        }
51        if let Some(t) =
52            app.session
53                .layers
54                .event(ctx, &app.cs, Mode::CycleNetwork, Some(&self.bottom_panel))
55        {
56            return t;
57        }
58        if let Outcome::Clicked(x) = self.bottom_panel.event(ctx) {
59            if x == "Experimental!" {
60                return Transition::Push(PopupMsg::new_state(ctx,"Caveats", vec![
61                    "Local streets are coloured all-or-nothing. If there are ANY shortcuts possible between main roads, they're red.",
62                    "",
63                    "Segregated cycle lanes are often mapped parallel to main roads, and don't show up clearly.",
64                    "",
65                    "Painted cycle lanes and bus lanes are treated the same. The safety and comfort of cycling in bus lanes varies regionally."
66                ]));
67            } else {
68                unreachable!()
69            }
70        }
71
72        ctx.canvas_movement();
73
74        Transition::Keep
75    }
76
77    fn draw(&self, g: &mut GfxCtx, app: &App) {
78        self.appwide_panel.draw(g);
79        self.bottom_panel.draw(g);
80        g.redraw(&self.draw_network);
81        app.per_map.draw_major_road_labels.draw(g);
82        app.session.layers.draw(g, app);
83        app.per_map.draw_all_filters.draw(g);
84        app.per_map.draw_poi_icons.draw(g);
85    }
86
87    fn recreate(&mut self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
88        Self::new_state(ctx, app)
89    }
90}
91
92fn help() -> Vec<&'static str> {
93    vec![
94        "This shows the cycle network, along with streets quiet enough to be comfortable cycling on.",
95    ]
96}
97
98fn draw_network(ctx: &mut EventCtx, app: &App) -> Drawable {
99    let map = &app.per_map.map;
100    let mut batch = GeomBatch::new();
101    let mut intersections = HashMap::new();
102    for road in map.all_roads() {
103        let mut bike_lane = false;
104        let mut bus_lane = false;
105        let mut buffer = road.is_cycleway();
106        for l in &road.lanes {
107            if l.lane_type == LaneType::Biking {
108                bike_lane = true;
109            } else if l.lane_type == LaneType::Bus {
110                bus_lane = true;
111            } else if matches!(l.lane_type, LaneType::Buffer(_)) {
112                buffer = true;
113            }
114        }
115
116        let color = if bike_lane && buffer {
117            *colors::NETWORK_SEGREGATED_LANE
118        } else if bike_lane || (bus_lane && map.get_config().bikes_can_use_bus_lanes) {
119            *colors::NETWORK_PAINTED_LANE
120        } else {
121            continue;
122        };
123
124        batch.push(color, road.get_thick_polygon());
125        // Arbitrarily pick a color when two different types of roads meet
126        intersections.insert(road.src_i, color);
127        intersections.insert(road.dst_i, color);
128    }
129
130    // Now calculate shortcuts through each neighbourhood interior
131    ctx.loading_screen("calculate shortcuts everywhere", |_, timer| {
132        let map = &app.per_map.map;
133        let partitioning = app.partitioning();
134        for (r, color) in timer
135            .parallelize(
136                "per neighbourhood",
137                partitioning.all_neighbourhoods().keys().collect(),
138                |id| {
139                    let neighbourhood = Neighbourhood::new_without_app(map, partitioning, *id);
140                    let mut result = Vec::new();
141                    for r in neighbourhood.interior_roads {
142                        let color = if neighbourhood.shortcuts.count_per_road.get(r) == 0 {
143                            *colors::NETWORK_QUIET_STREET
144                        } else {
145                            *colors::NETWORK_THROUGH_TRAFFIC_STREET
146                        };
147                        result.push((r, color));
148                    }
149                    result
150                },
151            )
152            .into_iter()
153            .flatten()
154        {
155            let road = map.get_r(r);
156            batch.push(color, road.get_thick_polygon());
157            intersections.insert(road.src_i, color);
158            intersections.insert(road.dst_i, color);
159        }
160    });
161
162    for (i, color) in intersections {
163        batch.push(color, map.get_i(i).polygon.clone());
164    }
165
166    batch.upload(ctx)
167}