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 intersections.insert(road.src_i, color);
127 intersections.insert(road.dst_i, color);
128 }
129
130 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}