1use std::collections::BTreeSet;
4
5use abstio::MapName;
6use geom::Polygon;
7use map_model::{IntersectionID, Map, RoadID};
8use widgetry::{lctrl, EventCtx, GfxCtx, Key, Line, Text, Widget};
9
10pub use self::camera::{CameraState, DefaultMap};
11pub use self::city_picker::CityPicker;
12pub use self::colors::{ColorDiscrete, ColorNetwork};
13pub use self::draw_overlapping_paths::draw_overlapping_paths;
14pub use self::heatmap::{draw_isochrone, make_heatmap, Grid, HeatmapOptions};
15pub use self::icons::{goal_marker, start_marker};
16pub use self::labels::{DrawRoadLabels, DrawSimpleRoadLabels};
17pub use self::minimap::{Minimap, MinimapControls};
18pub use self::navigate::Navigator;
19pub use self::polygon::EditPolygon;
20pub use self::title_screen::{Executable, TitleScreen};
21pub use self::trip_files::{TripManagement, TripManagementState};
22pub use self::ui::{
23 checkbox_per_mode, cmp_count, cmp_dist, cmp_duration, color_for_mode, percentage_bar,
24 FilePicker, FileSaver, FileSaverContents,
25};
26pub use self::waypoints::{InputWaypoints, WaypointID};
27use crate::AppLike;
28
29#[cfg(not(target_arch = "wasm32"))]
30pub use self::command::RunCommand;
31#[cfg(not(target_arch = "wasm32"))]
32pub use self::updater::prompt_to_download_missing_data;
33
34mod camera;
35mod city_picker;
36mod colors;
37#[cfg(not(target_arch = "wasm32"))]
38mod command;
39pub mod compare_counts;
40mod draw_overlapping_paths;
41mod heatmap;
42mod icons;
43#[cfg(not(target_arch = "wasm32"))]
44mod importer;
45mod labels;
46mod minimap;
47mod navigate;
48mod polygon;
49mod title_screen;
50mod trip_files;
51mod ui;
52#[cfg(not(target_arch = "wasm32"))]
53mod updater;
54mod waypoints;
55
56const NEXT_RELEASE: &str = "0.3.50";
58
59pub fn version() -> &'static str {
62 if cfg!(feature = "release_s3") {
63 NEXT_RELEASE
64 } else {
65 "dev"
66 }
67}
68
69pub fn loading_tips() -> Text {
71 Text::from_multiline(vec![
72 Line("Have you tried..."),
73 Line(""),
74 Line("- simulating cities in Britain, Taiwan, Poland, and more?"),
75 Line("- the 15-minute neighborhood tool?"),
76 Line("- exploring all of the map layers?"),
77 Line("- playing 15-minute Santa, our arcade game spin-off?"),
78 ])
79}
80
81pub fn grey_out_map(g: &mut GfxCtx, app: &dyn AppLike) {
83 g.fork_screenspace();
84 g.draw_polygon(
86 app.cs().fade_map_dark,
87 Polygon::rectangle(g.canvas.window_width, g.canvas.window_height),
88 );
89 g.unfork();
90}
91
92pub fn nice_map_name(name: &MapName) -> &str {
94 match name.city.country.as_ref() {
95 "au" => match (name.city.city.as_ref(), name.map.as_ref()) {
96 ("melbourne", "brunswick") => "Melbourne (Brunswick)",
97 ("melbourne", "dandenong") => "Melbourne (Dandenong)",
98 ("melbourne", "maribyrnong") => "Melbourne (Maribyrnong)",
99 _ => &name.map,
100 },
101 "at" => match (name.city.city.as_ref(), name.map.as_ref()) {
102 ("salzburg", "north") => "Salzburg (north)",
103 ("salzburg", "south") => "Salzburg (south)",
104 ("salzburg", "east") => "Salzburg (east)",
105 ("salzburg", "west") => "Salzburg (west)",
106 _ => &name.map,
107 },
108 "br" => match (name.city.city.as_ref(), name.map.as_ref()) {
109 ("sao_paulo", "aricanduva") => "São Paulo (Avenue Aricanduva)",
110 ("sao_paulo", "center") => "São Paulo (city center)",
111 ("sao_paulo", "sao_miguel_paulista") => "São Miguel Paulista",
112 _ => &name.map,
113 },
114 "ca" => match (name.city.city.as_ref(), name.map.as_ref()) {
115 ("montreal", "plateau") => "Montréal (Plateau)",
116 ("toronto", "dufferin") => "Toronto (Dufferin)",
117 ("toronto", "sw") => "Toronto (southwest)",
118 _ => &name.map,
119 },
120 "ch" => match (name.city.city.as_ref(), name.map.as_ref()) {
121 ("geneva", "center") => "Geneva",
122 ("zurich", "center") => "Zürich (city center)",
123 ("zurich", "north") => "Zürich (north)",
124 ("zurich", "south") => "Zürich (south)",
125 ("zurich", "east") => "Zürich (east)",
126 ("zurich", "west") => "Zürich (west)",
127 _ => &name.map,
128 },
129 "cl" => match (name.city.city.as_ref(), name.map.as_ref()) {
130 ("santiago", "bellavista") => "Bellavista (Santiago)",
131 _ => &name.map,
132 },
133 "cz" => match (name.city.city.as_ref(), name.map.as_ref()) {
134 ("frytek_mistek", "huge") => "Frýdek-Místek (entire area)",
135 _ => &name.map,
136 },
137 "de" => match (name.city.city.as_ref(), name.map.as_ref()) {
138 ("berlin", "center") => "Berlin (city center)",
139 ("berlin", "neukolln") => "Berlin-Neukölln",
140 ("bonn", "center") => "Bonn (city center)",
141 ("bonn", "nordstadt") => "Bonn (Nordstadt)",
142 ("bonn", "venusberg") => "Bonn (Venusberg)",
143 ("rostock", "center") => "Rostock",
144 _ => &name.map,
145 },
146 "fr" => match (name.city.city.as_ref(), name.map.as_ref()) {
147 ("brest", "city") => "Brest",
148 ("charleville_mezieres", "secteur1") => "Charleville-Mézières (secteur 1)",
149 ("charleville_mezieres", "secteur2") => "Charleville-Mézières (secteur 2)",
150 ("charleville_mezieres", "secteur3") => "Charleville-Mézières (secteur 3)",
151 ("charleville_mezieres", "secteur4") => "Charleville-Mézières (secteur 4)",
152 ("charleville_mezieres", "secteur5") => "Charleville-Mézières (secteur 5)",
153 ("lyon", "center") => "Lyon",
154 ("paris", "center") => "Paris (city center)",
155 ("paris", "north") => "Paris (north)",
156 ("paris", "south") => "Paris (south)",
157 ("paris", "east") => "Paris (east)",
158 ("paris", "west") => "Paris (west)",
159 ("strasbourg", "center") => "Strasbourg (center)",
160 ("strasbourg", "north") => "Strasbourg (north)",
161 ("strasbourg", "south") => "Strasbourg (south)",
162 _ => &name.map,
163 },
164 "gb" => match (name.city.city.as_ref(), name.map.as_ref()) {
165 ("allerton_bywater", "center") => "Allerton Bywater",
166 ("ashton_park", "center") => "Ashton Park",
167 ("aylesbury", "center") => "Aylesbury",
168 ("aylesham", "center") => "Aylesham",
169 ("bailrigg", "center") => "Bailrigg (Lancaster)",
170 ("bath_riverside", "center") => "Bath Riverside",
171 ("bicester", "center") => "Bicester",
172 ("birmingham", "center") => "Birmingham",
173 ("bournemouth", "center") => "Bournemouth",
174 ("bradford", "center") => "Bradford",
175 ("brighton", "center") => "Brighton",
176 ("brighton", "shoreham_by_sea") => "Shoreham-by-Sea",
177 ("bristol", "east") => "East Bristol",
178 ("bristol", "south") => "South Bristol",
179 ("burnley", "center") => "Burnley",
180 ("cambridge", "north") => "North Cambridge",
181 ("cardiff", "north") => "Cardiff",
182 ("castlemead", "center") => "Castlemead",
183 ("chapelford", "center") => "Chapelford (Cheshire)",
184 ("chapeltown_cohousing", "center") => "Chapeltown Cohousing",
185 ("chichester", "center") => "Chichester",
186 ("chorlton", "center") => "Chorlton",
187 ("clackers_brook", "center") => "Clackers Brook",
188 ("cricklewood", "center") => "Cricklewood",
189 ("culm", "center") => "Culm",
190 ("derby", "center") => "Derby",
191 ("dickens_heath", "center") => "Dickens Heath",
192 ("didcot", "center") => "Didcot (Harwell)",
193 ("dunton_hills", "center") => "Dunton Hills",
194 ("ebbsfleet", "center") => "Ebbsfleet (Dartford)",
195 ("edinburgh", "center") => "Edinburgh",
196 ("exeter_red_cow_village", "center") => "Exeter Red Cow Village",
197 ("glenrothes", "center") => "Glenrothes (Scotland)",
198 ("great_kneighton", "center") => "Great Kneighton (Cambridge)",
199 ("halsnhead", "center") => "Halsnead",
200 ("hampton", "center") => "Hampton",
201 ("inverness", "center") => "Inverness",
202 ("kergilliack", "center") => "Kergilliack",
203 ("keighley", "center") => "Keighley",
204 ("kidbrooke_village", "center") => "Kidbrooke Village",
205 ("lcid", "center") => "Leeds Climate Innovation District",
206 ("leeds", "central") => "Leeds (city center)",
207 ("leeds", "huge") => "Leeds (entire area inside motorways)",
208 ("leeds", "north") => "North Leeds",
209 ("leeds", "west") => "West Leeds",
210 ("lockleaze", "center") => "Lockleaze",
211 ("london", "camden") => "Camden",
212 ("london", "central") => "Central London",
213 ("london", "hackney") => "Hackney",
214 ("london", "kennington") => "Kennington (London)",
215 ("london", "kingston_upon_thames") => "Kingston upon Thames",
216 ("london", "southwark") => "Southwark",
217 ("long_marston", "center") => "Long Marston (Stratford)",
218 ("manchester", "levenshulme") => "Levenshulme (Manchester)",
219 ("manchester", "stockport") => "Stockport",
220 ("marsh_barton", "center") => "Marsh Barton",
221 ("micklefield", "center") => "Micklefield",
222 ("newborough_road", "center") => "Newborough Road",
223 ("newcastle_great_park", "center") => "Newcastle Great Park",
224 ("newcastle_upon_tyne", "center") => "Newcastle upon Tyne",
225 ("nottingham", "center") => "Nottingham (city center)",
226 ("nottingham", "huge") => "Nottingham (entire area)",
227 ("nottingham", "stapleford") => "Stapleford",
228 ("northwick_park", "center") => "Northwick Park",
229 ("oxford", "center") => "Oxford",
230 ("poundbury", "center") => "Poundbury",
231 ("priors_hall", "center") => "Priors Hall",
232 ("sheffield", "center") => "Sheffield",
233 ("sheffield", "darnall") => "Darnall",
234 ("st_albans", "center") => "St Albans",
235 ("taunton_firepool", "center") => "Taunton Firepool",
236 ("taunton_garden", "center") => "Taunton Garden",
237 ("tresham", "center") => "Tresham",
238 ("trumpington_meadows", "center") => "Trumpington Meadows",
239 ("tyersal_lane", "center") => "Tyersal Lane",
240 ("upton", "center") => "Upton",
241 ("water_lane", "center") => "Water Lane",
242 ("wichelstowe", "center") => "Wichelstowe",
243 ("wixams", "center") => "Wixams",
244 ("wokingham", "center") => "Wokingham",
245 ("wynyard", "center") => "Wynyard",
246 _ => &name.map,
247 },
248 "hk" => match (name.city.city.as_ref(), name.map.as_ref()) {
249 ("kowloon", "tsim_sha_tsui") => "Tsim Sha Tsui",
250 _ => &name.map,
251 },
252 "il" => match (name.city.city.as_ref(), name.map.as_ref()) {
253 ("tel_aviv", "center") => "Tel Aviv (city center)",
254 _ => &name.map,
255 },
256 "in" => match (name.city.city.as_ref(), name.map.as_ref()) {
257 ("pune", "center") => "Pune",
258 _ => &name.map,
259 },
260 "ir" => match (name.city.city.as_ref(), name.map.as_ref()) {
261 ("tehran", "parliament") => "Tehran (near Parliament)",
262 _ => &name.map,
263 },
264 "jp" => match (name.city.city.as_ref(), name.map.as_ref()) {
265 ("hiroshima", "uni") => "Hiroshima University",
266 ("tokyo", "shibuya") => "Shibuya",
267 _ => &name.map,
268 },
269 "kr" => match (name.city.city.as_ref(), name.map.as_ref()) {
270 ("seoul", "itaewon_dong") => "Itaewon Dong",
271 _ => &name.map,
272 },
273 "ly" => match (name.city.city.as_ref(), name.map.as_ref()) {
274 ("tripoli", "center") => "Tripoli",
275 _ => &name.map,
276 },
277 "nl" => match (name.city.city.as_ref(), name.map.as_ref()) {
278 ("groningen", "center") => "Groningen (city center)",
279 ("groningen", "huge") => "Groningen (entire area)",
280 _ => &name.map,
281 },
282 "nz" => match (name.city.city.as_ref(), name.map.as_ref()) {
283 ("auckland", "mangere") => "Māngere (Auckland)",
284 _ => &name.map,
285 },
286 "pl" => match (name.city.city.as_ref(), name.map.as_ref()) {
287 ("krakow", "center") => "Kraków (city center)",
288 ("warsaw", "center") => "Warsaw (city center)",
289 _ => &name.map,
290 },
291 "pt" => match (name.city.city.as_ref(), name.map.as_ref()) {
292 ("lisbon", "center") => "Lisbon (city center)",
293 _ => &name.map,
294 },
295 "sg" => match (name.city.city.as_ref(), name.map.as_ref()) {
296 ("jurong", "center") => "Jurong",
297 _ => &name.map,
298 },
299 "tw" => match (name.city.city.as_ref(), name.map.as_ref()) {
300 ("keelung", "center") => "Keelung",
301 ("taipei", "center") => "Taipei (city center)",
302 _ => &name.map,
303 },
304 "us" => match (name.city.city.as_ref(), name.map.as_ref()) {
305 ("anchorage", "downtown") => "Anchorage",
306 ("bellevue", "huge") => "Bellevue",
307 ("beltsville", "i495") => "I-495 in Beltsville, MD",
308 ("detroit", "downtown") => "Detroit",
309 ("lynnwood", "hazelwood") => "Lynnwood, WA",
310 ("milwaukee", "downtown") => "Downtown Milwaukee",
311 ("milwaukee", "oak_creek") => "Oak Creek",
312 ("missoula", "center") => "Missoula",
313 ("mt_vernon", "burlington") => "Burlington",
314 ("mt_vernon", "downtown") => "Mt. Vernon",
315 ("new_haven", "center") => "New Haven",
316 ("nyc", "fordham") => "Fordham",
317 ("nyc", "lower_manhattan") => "Lower Manhattan",
318 ("nyc", "midtown_manhattan") => "Midtown Manhattan",
319 ("nyc", "downtown_brooklyn") => "Downtown Brooklyn",
320 ("phoenix", "gilbert") => "Gilbert",
321 ("phoenix", "tempe") => "Tempe",
322 ("providence", "downtown") => "Providence",
323 ("san_francisco", "downtown") => "San Francisco",
324 ("seattle", "arboretum") => "Arboretum",
325 ("seattle", "central_seattle") => "Central Seattle",
326 ("seattle", "downtown") => "Downtown Seattle",
327 ("seattle", "huge_seattle") => "Seattle (entire area)",
328 ("seattle", "lakeslice") => "Lake Washington corridor",
329 ("seattle", "montlake") => "Montlake and Eastlake",
330 ("seattle", "north_seattle") => "North Seattle",
331 ("seattle", "phinney") => "Phinney Ridge",
332 ("seattle", "qa") => "Queen Anne",
333 ("seattle", "slu") => "South Lake Union",
334 ("seattle", "south_seattle") => "South Seattle",
335 ("seattle", "udistrict_ravenna") => "University District",
336 ("seattle", "wallingford") => "Wallingford",
337 ("seattle", "west_seattle") => "West Seattle",
338 ("tucson", "center") => "Tucson",
339 _ => &name.map,
340 },
341 _ => &name.map,
342 }
343}
344
345pub fn nice_country_name(code: &str) -> &str {
346 match code {
349 "au" => "Australia",
350 "at" => "Austria",
351 "br" => "Brazil",
352 "ca" => "Canada",
353 "ch" => "Switzerland",
354 "cl" => "Chile",
355 "cz" => "Czech Republic",
356 "de" => "Germany",
357 "fr" => "France",
358 "gb" => "Great Britain",
359 "il" => "Israel",
360 "in" => "India",
361 "ir" => "Iran",
362 "hk" => "Hong Kong",
363 "jp" => "Japan",
364 "kr" => "South Korea",
365 "ly" => "Libya",
366 "nl" => "Netherlands",
367 "nz" => "New Zealand",
368 "pl" => "Poland",
369 "pt" => "Portugal",
370 "sg" => "Singapore",
371 "tw" => "Taiwan",
372 "us" => "United States of America",
373 _ => code,
374 }
375}
376
377pub fn find_exe(cmd: &str) -> String {
379 let mut directories = Vec::new();
380 for arch in ["x86_64-unknown-linux-gnu", ""] {
382 for mode in ["release", "debug"] {
385 for relative_dir in [".", "..", "../.."] {
386 directories.push(
387 std::path::Path::new(relative_dir)
388 .join("target")
389 .join(arch)
390 .join(mode)
391 .display()
392 .to_string(),
393 );
394 }
395 }
396 }
397 directories.push("./binaries".to_string());
399
400 for dir in directories {
401 let path = if cfg!(windows) {
404 format!("{}/{}.exe", dir, cmd).replace("/", "\\")
405 } else {
406 format!("{}/{}", dir, cmd)
407 };
408 if let Ok(metadata) = fs_err::metadata(&path) {
409 if metadata.is_file() {
410 return path;
411 } else {
412 debug!(
413 "found matching path: {}/{} but it's not a file.",
414 &path, cmd
415 );
416 }
417 }
418 }
419 panic!("Couldn't find the {} executable. Is it built?", cmd);
420}
421
422pub fn change_map_btn(ctx: &EventCtx, app: &dyn AppLike) -> Widget {
424 ctx.style()
425 .btn_popup_icon_text(
426 "system/assets/tools/map.svg",
427 nice_map_name(app.map().get_name()),
428 )
429 .hotkey(lctrl(Key::L))
430 .build_widget(ctx, "change map")
431}
432
433pub fn home_btn(ctx: &EventCtx) -> Widget {
435 ctx.style()
436 .btn_plain
437 .btn()
438 .image_path("system/assets/pregame/logo.svg")
439 .image_dims(50.0)
440 .build_widget(ctx, "Home")
441}
442
443pub fn app_header(ctx: &EventCtx, app: &dyn AppLike, title: &str) -> Widget {
446 Widget::col(vec![
447 Widget::row(vec![
448 home_btn(ctx),
449 Line(title).small_heading().into_widget(ctx).centered_vert(),
450 ]),
451 change_map_btn(ctx, app),
452 ])
453}
454
455pub fn intersections_from_roads(roads: &BTreeSet<RoadID>, map: &Map) -> BTreeSet<IntersectionID> {
456 let mut results = BTreeSet::new();
457 for r in roads {
458 let r = map.get_r(*r);
459 for i in [r.src_i, r.dst_i] {
460 if results.contains(&i) {
461 continue;
462 }
463 if map.get_i(i).roads.iter().all(|r| roads.contains(r)) {
464 results.insert(i);
465 }
466 }
467 }
468 results
469}
470
471pub fn update_url_map_name(app: &dyn AppLike) {
473 widgetry::tools::URLManager::update_url_free_param(
474 app.map()
475 .get_name()
476 .path()
477 .strip_prefix(&abstio::path(""))
478 .unwrap()
479 .to_string(),
480 );
481}