1use std::collections::HashSet;
2
3use abstio::MapName;
4use abstutil::{Tags, Timer};
5use blockfinding::Perimeter;
6use geom::{ArrowCap, Circle, Distance, PolyLine, Pt2D};
7use map_gui::colors::ColorSchemeChoice;
8use map_gui::load::MapLoader;
9use map_gui::options::OptionsPanel;
10use map_gui::render::{calculate_corners, DrawMap, DrawOptions};
11use map_gui::AppLike;
12use map_model::{
13 ControlTrafficSignal, IntersectionID, PathConstraints, Position, RoadID, NORMAL_LANE_THICKNESS,
14};
15use sim::Sim;
16use synthpop::TripEndpoint;
17use widgetry::tools::{ChooseSomething, PopupMsg, PromptInput};
18use widgetry::{
19 lctrl, Cached, Choice, Color, DrawBaselayer, Drawable, EventCtx, GeomBatch, GfxCtx,
20 HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, Toggle, UpdateType,
21 VerticalAlignment, Widget,
22};
23
24use crate::app::{App, ShowLayers, ShowObject, Transition};
25use crate::common::{tool_panel, CommonState};
26use crate::info::ContextualActions;
27use crate::sandbox::GameplayMode;
28use crate::ID;
29
30pub use self::routes::PathCostDebugger;
31
32mod blocked_by;
33mod blockfinder;
34mod floodfill;
35mod objects;
36pub mod path_counter;
37mod polygons;
38mod routes;
39mod select_roads;
40mod uber_turns;
41
42pub struct DebugMode {
43 panel: Panel,
44 common: CommonState,
45 tool_panel: Panel,
46 objects: objects::ObjectDebugger,
47 hidden: HashSet<ID>,
48 layers: ShowLayers,
49 search_results: Option<SearchResults>,
50 all_routes: Option<(usize, Drawable)>,
51
52 highlighted_agents: Cached<IntersectionID, Drawable>,
53}
54
55impl DebugMode {
56 pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
57 Box::new(DebugMode {
58 panel: Panel::new_builder(Widget::col(vec![
59 Widget::row(vec![
60 Line("Debug Mode").small_heading().into_widget(ctx),
61 ctx.style().btn_close_widget(ctx),
62 ]),
63 Widget::placeholder(ctx, "current info"),
64 Toggle::switch(ctx, "show buildings", Key::Num1, true),
65 Toggle::switch(ctx, "show intersections", Key::Num2, true),
66 Toggle::switch(ctx, "show lanes", Key::Num3, true),
67 Toggle::switch(ctx, "show areas", Key::Num4, true),
68 Toggle::switch(ctx, "show labels", Key::Num5, false),
69 Toggle::switch(ctx, "show route for all agents", lctrl(Key::R), false),
70 Toggle::switch(
71 ctx,
72 "screen recording mode",
73 lctrl(Key::H),
74 app.opts.minimal_controls,
75 ),
76 Widget::col(vec![
77 ctx.style()
78 .btn_outline
79 .text("unhide everything")
80 .hotkey(lctrl(Key::H))
81 .build_def(ctx),
82 ctx.style()
83 .btn_outline
84 .text("screenshot all of the everything")
85 .build_def(ctx),
86 ctx.style()
87 .btn_outline
88 .text("search OSM metadata")
89 .hotkey(Key::Slash)
90 .build_def(ctx),
91 ctx.style()
92 .btn_outline
93 .text("clear OSM search results")
94 .hotkey(Key::Slash)
95 .build_def(ctx),
96 ctx.style()
97 .btn_outline
98 .text("save sim state")
99 .build_def(ctx),
100 ctx.style()
101 .btn_outline
102 .text("load previous sim state")
103 .build_def(ctx),
104 ctx.style()
105 .btn_outline
106 .text("load next sim state")
107 .build_def(ctx),
108 ctx.style()
109 .btn_outline
110 .text("pick a savestate to load")
111 .build_def(ctx),
112 ctx.style()
113 .btn_outline
114 .text("find bad traffic signals")
115 .build_def(ctx),
116 ctx.style()
117 .btn_outline
118 .text("find degenerate roads")
119 .build_def(ctx),
120 ctx.style()
121 .btn_outline
122 .text("find large intersections")
123 .build_def(ctx),
124 ctx.style()
125 .btn_outline
126 .text("sim internal stats")
127 .build_def(ctx),
128 ctx.style()
129 .btn_outline
130 .text("blocked-by graph")
131 .build_def(ctx),
132 ctx.style()
133 .btn_outline
134 .text("blockfinder")
135 .hotkey(lctrl(Key::B))
136 .build_def(ctx),
137 ctx.style()
138 .btn_outline
139 .text("render to GeoJSON")
140 .hotkey(Key::G)
141 .build_def(ctx),
142 ctx.style()
143 .btn_outline
144 .text("export geometry to GeoJSON")
145 .build_def(ctx),
146 ctx.style()
147 .btn_outline
148 .text("draw banned turns")
149 .hotkey(Key::T)
150 .build_def(ctx),
151 ctx.style()
152 .btn_outline
153 .text("draw arterial crosswalks")
154 .hotkey(Key::W)
155 .build_def(ctx),
156 ctx.style()
157 .btn_outline
158 .text("export color-scheme")
159 .build_def(ctx),
160 ctx.style()
161 .btn_outline
162 .text("import color-scheme")
163 .build_def(ctx),
164 ctx.style()
165 .btn_outline
166 .text("find bad intersection polygons")
167 .build_def(ctx),
168 ]),
169 Text::from_all(vec![
170 Line("Hold "),
171 Key::LeftControl.txt(ctx),
172 Line(" to show position"),
173 ])
174 .into_widget(ctx),
175 ]))
176 .aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
177 .build(ctx),
178 common: CommonState::new(),
179 tool_panel: tool_panel(ctx),
180 objects: objects::ObjectDebugger,
181 hidden: HashSet::new(),
182 layers: ShowLayers::new(),
183 search_results: None,
184 all_routes: None,
185 highlighted_agents: Cached::new(),
186 })
187 }
188
189 fn reset_info(&mut self, ctx: &mut EventCtx) {
190 let mut txt = Text::new();
191 if !self.hidden.is_empty() {
192 txt.add_line(format!("Hiding {} things", self.hidden.len()));
193 }
194 if let Some(ref results) = self.search_results {
195 txt.add_line(format!(
196 "Search for {} has {} results",
197 results.query, results.num_matches
198 ));
199 }
200 if let Some((n, _)) = self.all_routes {
201 txt.add_line(format!("Showing {} routes", abstutil::prettyprint_usize(n)));
202 }
203 self.panel
204 .replace(ctx, "current info", txt.into_widget(ctx));
205 }
206}
207
208impl State<App> for DebugMode {
209 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
210 ctx.canvas_movement();
211
212 if ctx.redo_mouseover() {
213 app.primary.current_selection = app.mouseover_debug_mode(ctx, self);
214 }
215
216 match self.panel.event(ctx) {
217 Outcome::Clicked(x) => match x.as_ref() {
218 "close" => {
219 return Transition::Pop;
220 }
221 "save sim state" => {
222 ctx.loading_screen("savestate", |_, timer| {
223 timer.start("save sim state");
224 app.primary.sim.save();
225 timer.stop("save sim state");
226 });
227 }
228 "load previous sim state" => {
229 if let Some(t) = ctx.loading_screen("load previous savestate", |ctx, timer| {
230 let prev_state = app
231 .primary
232 .sim
233 .find_previous_savestate(app.primary.sim.time());
234 match prev_state
235 .clone()
236 .and_then(|path| Sim::load_savestate(path, timer).ok())
237 {
238 Some(new_sim) => {
239 app.primary.sim = new_sim;
240 app.recalculate_current_selection(ctx);
241 None
242 }
243 None => Some(Transition::Push(PopupMsg::new_state(
244 ctx,
245 "Error",
246 vec![format!("Couldn't load previous savestate {:?}", prev_state)],
247 ))),
248 }
249 }) {
250 return t;
251 }
252 }
253 "load next sim state" => {
254 if let Some(t) = ctx.loading_screen("load next savestate", |ctx, timer| {
255 let next_state =
256 app.primary.sim.find_next_savestate(app.primary.sim.time());
257 match next_state
258 .clone()
259 .and_then(|path| Sim::load_savestate(path, timer).ok())
260 {
261 Some(new_sim) => {
262 app.primary.sim = new_sim;
263 app.recalculate_current_selection(ctx);
264 None
265 }
266 None => Some(Transition::Push(PopupMsg::new_state(
267 ctx,
268 "Error",
269 vec![format!("Couldn't load next savestate {:?}", next_state)],
270 ))),
271 }
272 }) {
273 return t;
274 }
275 }
276 "pick a savestate to load" => {
277 return Transition::Push(ChooseSomething::new_state(
278 ctx,
279 "Load which savestate?",
280 Choice::strings(abstio::list_all_objects(app.primary.sim.save_dir())),
281 Box::new(|ss, ctx, app| {
282 let ss_path = format!("{}/{}.bin", app.primary.sim.save_dir(), ss);
284
285 ctx.loading_screen("load savestate", |ctx, timer| {
286 app.primary.sim = Sim::load_savestate(ss_path, timer)
287 .expect("Can't load savestate");
288 app.recalculate_current_selection(ctx);
289 });
290 Transition::Pop
291 }),
292 ));
293 }
294 "unhide everything" => {
295 self.hidden.clear();
296 app.primary.current_selection = app.mouseover_debug_mode(ctx, self);
297 self.reset_info(ctx);
298 }
299 "search OSM metadata" => {
300 return Transition::Push(PromptInput::new_state(
301 ctx,
302 "Search for what?",
303 String::new(),
304 Box::new(search_osm),
305 ));
306 }
307 "clear OSM search results" => {
308 self.search_results = None;
309 self.reset_info(ctx);
310 }
311 "screenshot all of the everything" => {
312 return Transition::Push(ScreenshotTest::new_state(
313 ctx,
314 app,
315 vec![
316 MapName::seattle("downtown"),
317 MapName::seattle("montlake"),
318 MapName::new("gb", "london", "kennington"),
319 MapName::new("pl", "krakow", "center"),
320 MapName::new("us", "phoenix", "tempe"),
321 ],
322 ));
323 }
324 "find bad traffic signals" => {
325 find_bad_signals(app);
326 }
327 "find degenerate roads" => {
328 find_degenerate_roads(app);
329 }
330 "find large intersections" => {
331 find_large_intersections(app);
332 }
333 "sim internal stats" => {
334 return Transition::Push(PopupMsg::new_state(
335 ctx,
336 "Simulation internal stats",
337 app.primary.sim.describe_internal_stats(),
338 ));
339 }
340 "blocked-by graph" => {
341 return Transition::Push(blocked_by::Viewer::new_state(ctx, app));
342 }
343 "blockfinder" => {
344 app.primary.current_selection = None;
345 return Transition::Push(blockfinder::Blockfinder::new_state(ctx, app));
346 }
347 "render to GeoJSON" => {
348 ctx.loading_screen("render to GeoJSON", |ctx, timer| {
351 timer.start("render");
352 let batch = DrawMap::zoomed_batch(ctx, app);
353 let features = batch.into_geojson(Some(app.primary.map.get_gps_bounds()));
354 let geojson = geojson::GeoJson::from(geojson::FeatureCollection {
355 bbox: None,
356 features,
357 foreign_members: None,
358 });
359 abstio::write_json("rendered_map.json".to_string(), &geojson);
360 timer.stop("render");
361 });
362 }
363 "export geometry to GeoJSON" => {
364 abstio::write_json(
365 "map_geometry.json".to_string(),
366 &app.primary.map.export_geometry(),
367 );
368 }
369 "draw banned turns" => {
370 self.search_results = Some(SearchResults {
372 query: "banned turns".to_string(),
373 num_matches: 0,
374 draw: draw_banned_turns(ctx, app),
375 });
376 self.reset_info(ctx);
377 }
378 "draw arterial crosswalks" => {
379 self.search_results = Some(SearchResults {
380 query: "wide crosswalks".to_string(),
381 num_matches: 0,
382 draw: draw_arterial_crosswalks(ctx, app),
383 });
384 self.reset_info(ctx);
385 }
386 "export color-scheme" => {
387 app.cs.export("color_scheme").unwrap();
388 }
389 "import color-scheme" => {
390 app.cs.import("color_scheme").unwrap();
391 ctx.loading_screen("rerendering map colors", |ctx, timer| {
392 app.primary.draw_map =
393 DrawMap::new(ctx, &app.primary.map, &app.opts, &app.cs, timer);
394 });
395 }
396 "find bad intersection polygons" => {
397 self.search_results = Some(SearchResults {
398 query: "bad intersection polygons".to_string(),
399 num_matches: 0,
400 draw: draw_bad_intersections(ctx, app),
401 });
402 self.reset_info(ctx);
403 }
404 _ => unreachable!(),
405 },
406 Outcome::Changed(_) => {
407 self.layers.show_buildings = self.panel.is_checked("show buildings");
409 self.layers.show_intersections = self.panel.is_checked("show intersections");
410 self.layers.show_lanes = self.panel.is_checked("show lanes");
411 self.layers.show_areas = self.panel.is_checked("show areas");
412 self.layers.show_labels = self.panel.is_checked("show labels");
413 app.opts.minimal_controls = self.panel.is_checked("screen recording mode");
414 if self.panel.is_checked("show route for all agents") {
415 if self.all_routes.is_none() {
416 self.all_routes = Some(calc_all_routes(ctx, app));
417 self.reset_info(ctx);
418 }
419 } else if self.all_routes.is_some() {
420 self.all_routes = None;
421 self.reset_info(ctx);
422 }
423 }
424 _ => {}
425 }
426
427 self.highlighted_agents.update(
428 match app.primary.current_selection {
429 Some(ID::Intersection(i)) => Some(i),
430 _ => None,
431 },
432 |key| {
433 let mut batch = GeomBatch::new();
434 for (a, _) in app.primary.sim.get_accepted_agents(key) {
435 if let Some(outline) = app.primary.get_obj_outline(
436 ctx,
437 ID::from_agent(a),
438 &app.cs,
439 &app.primary.map,
440 &mut app.primary.agents.borrow_mut(),
441 ) {
442 batch.push(Color::PURPLE, outline);
443 } else {
444 warn!(
445 "{} is accepted at or blocked by by {:?}, but no longer exists",
446 a, key
447 );
448 }
449 }
450 ctx.upload(batch)
451 },
452 );
453
454 if let Some(t) = self.common.event(ctx, app, &mut Actions {}) {
455 return t;
456 }
457 match self.tool_panel.event(ctx) {
458 Outcome::Clicked(x) => match x.as_ref() {
459 "back" => Transition::Pop,
460 "settings" => Transition::Push(OptionsPanel::new_state(ctx, app)),
461 _ => unreachable!(),
462 },
463 _ => Transition::Keep,
464 }
465 }
466
467 fn draw_baselayer(&self) -> DrawBaselayer {
468 DrawBaselayer::Custom
469 }
470
471 fn draw(&self, g: &mut GfxCtx, app: &App) {
472 let mut opts = DrawOptions::new();
473 opts.label_buildings = self.layers.show_labels;
474 app.draw(g, opts, self);
475
476 if let Some(ref results) = self.search_results {
477 g.redraw(&results.draw);
478 }
479 if let Some(draw) = self.highlighted_agents.value() {
480 g.redraw(draw);
481 }
482
483 self.objects.draw(g, app);
484 if let Some((_, ref draw)) = self.all_routes {
485 g.redraw(draw);
486 }
487
488 if !g.is_screencap() {
489 self.panel.draw(g);
490 self.common.draw(g, app);
491 self.tool_panel.draw(g);
492 }
493 }
494}
495
496impl ShowObject for DebugMode {
497 fn show(&self, obj: &ID) -> bool {
498 if self.hidden.contains(obj) {
499 return false;
500 }
501
502 match obj {
503 ID::Road(_) | ID::Lane(_) => self.layers.show_lanes,
504 ID::Building(_) => self.layers.show_buildings,
505 ID::Intersection(_) => self.layers.show_intersections,
506 ID::Area(_) => self.layers.show_areas,
507 _ => true,
508 }
509 }
510
511 fn layers(&self) -> &ShowLayers {
512 &self.layers
513 }
514}
515
516fn search_osm(filter: String, ctx: &mut EventCtx, app: &mut App) -> Transition {
517 let mut num_matches = 0;
518 let mut batch = GeomBatch::new();
519
520 let map = &app.primary.map;
522 let color = Color::RED.alpha(0.8);
523 for r in map.all_roads() {
524 if r.osm_tags
525 .inner()
526 .iter()
527 .any(|(k, v)| format!("{} = {}", k, v).contains(&filter))
528 {
529 num_matches += 1;
530 batch.push(color, r.get_thick_polygon());
531 }
532 }
533 for a in map.all_areas() {
534 if a.osm_tags
535 .inner()
536 .iter()
537 .any(|(k, v)| format!("{} = {}", k, v).contains(&filter))
538 {
539 num_matches += 1;
540 batch.push(color, a.polygon.clone());
541 }
542 }
543
544 let results = SearchResults {
545 query: filter,
546 num_matches,
547 draw: batch.upload(ctx),
548 };
549
550 Transition::Multi(vec![
551 Transition::Pop,
552 Transition::ModifyState(Box::new(|state, ctx, _| {
553 let mode = state.downcast_mut::<DebugMode>().unwrap();
554 mode.search_results = Some(results);
555 mode.reset_info(ctx);
556 })),
557 ])
558}
559
560struct SearchResults {
561 query: String,
562 num_matches: usize,
563 draw: Drawable,
564}
565
566fn calc_all_routes(ctx: &EventCtx, app: &mut App) -> (usize, Drawable) {
567 let agents = app.primary.sim.active_agents();
568 let mut batch = GeomBatch::new();
569 let mut cnt = 0;
570 let sim = &app.primary.sim;
571 let map = &app.primary.map;
572 for trace in Timer::new("calculate all routes")
573 .parallelize("route to geometry", agents, |id| {
574 sim.trace_route(id, map)
575 .map(|trace| trace.make_polygons(NORMAL_LANE_THICKNESS))
576 })
577 .into_iter()
578 .flatten()
579 {
580 cnt += 1;
581 batch.push(app.cs.route, trace);
582 }
583 (cnt, ctx.upload(batch))
584}
585
586struct Actions;
587impl ContextualActions for Actions {
588 fn actions(&self, app: &App, id: ID) -> Vec<(Key, String)> {
589 let mut actions = vec![
590 (Key::D, "debug".to_string()),
591 (Key::V, "debug with JSON viewer".to_string()),
592 ];
593 match id {
594 ID::Lane(l) => {
595 actions.push((Key::H, "hide this".to_string()));
596 if app.primary.map.get_l(l).lane_type.supports_any_movement() {
597 actions.push((Key::F, "floodfill from this lane".to_string()));
598 actions.push((Key::S, "show strongly-connected components".to_string()));
599 }
600 actions.push((Key::X, "debug lane geometry".to_string()));
601 actions.push((Key::F2, "debug lane triangles geometry".to_string()));
602 actions.push((Key::C, "export roads".to_string()));
603 actions.push((Key::E, "show equiv_pos".to_string()));
604 actions.push((Key::B, "trace this block".to_string()));
605 }
606 ID::Intersection(i) => {
607 actions.push((Key::H, "hide this".to_string()));
608 actions.push((Key::X, "debug intersection geometry".to_string()));
609 actions.push((Key::F2, "debug sidewalk corners".to_string()));
610 if app.primary.map.get_i(i).roads.len() == 2 {
611 actions.push((Key::C, "collapse degenerate road?".to_string()));
612 }
613 if app.primary.map.get_i(i).is_border() {
614 actions.push((Key::R, "route from here".to_string()));
615 }
616 actions.push((Key::U, "explore uber-turns".to_string()));
617 }
618 ID::Car(_) => {
619 actions.push((Key::Backspace, "forcibly delete this car".to_string()));
620 }
621 ID::Area(_) => {
622 actions.push((Key::X, "debug area geometry".to_string()));
623 actions.push((Key::F2, "debug area triangles".to_string()));
624 }
625 ID::ParkingLot(_) => {
626 actions.push((Key::H, "hide this".to_string()));
627 }
628 ID::TransitStop(_) => {
629 actions.push((Key::H, "hide this".to_string()));
630 }
631 ID::Building(_) => {
632 actions.push((Key::R, "route from here".to_string()));
633 }
634 _ => {}
635 }
636 actions
637 }
638
639 fn execute(
640 &mut self,
641 ctx: &mut EventCtx,
642 app: &mut App,
643 id: ID,
644 action: String,
645 close_info: &mut bool,
646 ) -> Transition {
647 match (id, action.as_ref()) {
648 (id, "hide this") => Transition::ModifyState(Box::new(|state, ctx, app| {
649 let mode = state.downcast_mut::<DebugMode>().unwrap();
650 println!("Hiding {:?}", id);
651 app.primary.current_selection = None;
652 mode.hidden.insert(id);
653 mode.reset_info(ctx);
654 })),
655 (id, "debug") => {
656 *close_info = false;
657 objects::ObjectDebugger::dump_debug(id, &app.primary.map, &app.primary.sim);
658 Transition::Keep
659 }
660 (id, "debug with JSON viewer") => {
661 *close_info = false;
662 objects::ObjectDebugger::debug_json(id, &app.primary.map, &app.primary.sim);
663 Transition::Keep
664 }
665 (ID::Car(c), "forcibly delete this car") => {
666 app.primary.sim.delete_car(c, &app.primary.map);
667 app.primary
668 .sim
669 .tiny_step(&app.primary.map, &mut app.primary.sim_cb);
670 app.primary.current_selection = None;
671 Transition::Keep
672 }
673 (ID::Lane(l), "floodfill from this lane") => {
674 Transition::Push(floodfill::Floodfiller::floodfill(ctx, app, l))
675 }
676 (ID::Lane(l), "show strongly-connected components") => {
677 Transition::Push(floodfill::Floodfiller::scc(ctx, app, l))
678 }
679 (ID::Intersection(i), "debug intersection geometry") => {
680 let pts = app
681 .primary
682 .map
683 .get_i(i)
684 .polygon
685 .get_outer_ring()
686 .clone()
687 .into_points();
688 let mut pts_without_last = pts.clone();
689 pts_without_last.pop();
690 Transition::Push(polygons::PolygonDebugger::new_state(
691 ctx,
692 "point",
693 pts.iter().map(|pt| polygons::Item::Point(*pt)).collect(),
694 Some(Pt2D::center(&pts_without_last)),
695 ))
696 }
697 (ID::Intersection(i), "debug sidewalk corners") => {
698 Transition::Push(polygons::PolygonDebugger::new_state(
699 ctx,
700 "corner",
701 calculate_corners(app.primary.map.get_i(i), &app.primary.map)
702 .into_iter()
703 .map(polygons::Item::Polygon)
704 .collect(),
705 None,
706 ))
707 }
708 (ID::Intersection(i), "collapse degenerate road?") => {
709 let i = app.primary.map.get_i(i);
710 let (r1, r2) = {
711 let mut iter = i.roads.iter();
712 (*iter.next().unwrap(), *iter.next().unwrap())
713 };
714 diff_tags(
715 &app.primary.map.get_r(r1).osm_tags,
716 &app.primary.map.get_r(r2).osm_tags,
717 );
718 Transition::Keep
719 }
720 (ID::Intersection(i), "route from here") => Transition::Push(
721 routes::RouteExplorer::new_state(ctx, app, TripEndpoint::Border(i)),
722 ),
723 (ID::Intersection(i), "explore uber-turns") => {
724 Transition::Push(uber_turns::UberTurnPicker::new_state(ctx, app, i))
725 }
726 (ID::Lane(l), "debug lane geometry") => {
727 Transition::Push(polygons::PolygonDebugger::new_state(
728 ctx,
729 "point",
730 app.primary
731 .map
732 .get_l(l)
733 .lane_center_pts
734 .points()
735 .iter()
736 .map(|pt| polygons::Item::Point(*pt))
737 .collect(),
738 None,
739 ))
740 }
741 (ID::Lane(l), "debug lane triangles geometry") => {
742 Transition::Push(polygons::PolygonDebugger::new_state(
743 ctx,
744 "triangle",
745 app.primary
746 .draw_map
747 .get_l(l)
748 .polygon
749 .triangles()
750 .into_iter()
751 .map(polygons::Item::Triangle)
752 .collect(),
753 None,
754 ))
755 }
756 (ID::Lane(l), "export roads") => {
757 Transition::Push(select_roads::BulkSelect::new_state(ctx, app, l.road))
758 }
759 (ID::Lane(l), "show equiv_pos") => {
760 Transition::ModifyState(Box::new(move |state, ctx, app| {
761 if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
762 let map = &app.primary.map;
763 let pl = &map.get_l(l).lane_center_pts;
764 if let Some((dist, _)) = pl.dist_along_of_point(pl.project_pt(pt)) {
765 let base_pos = Position::new(l, dist);
766 let mut batch = GeomBatch::new();
767 for l in &map.get_parent(l).lanes {
768 let pt = base_pos.equiv_pos(l.id, map).pt(map);
769 batch.push(
770 Color::RED,
771 Circle::new(pt, Distance::meters(1.0)).to_polygon(),
772 );
773 }
774 let mode = state.downcast_mut::<DebugMode>().unwrap();
775 mode.search_results = Some(SearchResults {
777 query: format!("equiv_pos {}", base_pos),
778 num_matches: 0,
779 draw: ctx.upload(batch),
780 });
781 }
782 }
783 }))
784 }
785 (ID::Lane(l), "trace this block") => {
786 app.primary.current_selection = None;
787 let map = &app.primary.map;
788 return Transition::Push(
789 match Perimeter::single_block(
790 map,
791 l,
792 &Perimeter::find_roads_to_skip_tracing(map),
793 )
794 .and_then(|perim| perim.to_block(map))
795 {
796 Ok(block) => blockfinder::OneBlock::new_state(ctx, app, block),
797 Err(err) => {
798 error!("Blockfinding failed: {}", err);
800 PopupMsg::new_state(ctx, "Error", vec!["See console"])
801 }
802 },
803 );
804 }
805 (ID::Area(a), "debug area geometry") => {
806 let pts = app
807 .primary
808 .map
809 .get_a(a)
810 .polygon
811 .get_outer_ring()
812 .clone()
813 .into_points();
814 let center = if pts[0] == *pts.last().unwrap() {
815 Pt2D::center(&pts.iter().skip(1).cloned().collect::<Vec<_>>())
818 } else {
819 Pt2D::center(&pts)
820 };
821 Transition::Push(polygons::PolygonDebugger::new_state(
822 ctx,
823 "point",
824 pts.iter().map(|pt| polygons::Item::Point(*pt)).collect(),
825 Some(center),
826 ))
827 }
828 (ID::Area(a), "debug area triangles") => {
829 Transition::Push(polygons::PolygonDebugger::new_state(
830 ctx,
831 "triangle",
832 app.primary
833 .map
834 .get_a(a)
835 .polygon
836 .triangles()
837 .into_iter()
838 .map(polygons::Item::Triangle)
839 .collect(),
840 None,
841 ))
842 }
843 (ID::Building(b), "route from here") => Transition::Push(
844 routes::RouteExplorer::new_state(ctx, app, TripEndpoint::Building(b)),
845 ),
846 _ => unreachable!(),
847 }
848 }
849
850 fn is_paused(&self) -> bool {
851 true
852 }
853
854 fn gameplay_mode(&self) -> GameplayMode {
855 GameplayMode::FixTrafficSignals
857 }
858}
859
860fn find_bad_signals(app: &App) {
861 error!("Bad traffic signals:");
862 for i in app.primary.map.all_intersections() {
863 if i.is_traffic_signal() {
864 let first = &ControlTrafficSignal::get_possible_policies(&app.primary.map, i.id)[0].0;
865 if first == "stage per road" || first == "arbitrary assignment" {
866 error!("- {}", i.id);
867 }
868 }
869 }
870}
871
872fn find_degenerate_roads(app: &App) {
875 let map = &app.primary.map;
876 for i in map.all_intersections() {
877 if i.roads.len() != 2 {
878 continue;
879 }
880 let (r1, r2) = {
881 let mut iter = i.roads.iter();
882 (*iter.next().unwrap(), *iter.next().unwrap())
883 };
884 let r1 = map.get_r(r1);
885 let r2 = map.get_r(r2);
886 if r1.zorder != r2.zorder {
887 continue;
888 }
889 if r1
890 .lanes
891 .iter()
892 .map(|l| (l.dir, l.lane_type))
893 .collect::<Vec<_>>()
894 != r2
895 .lanes
896 .iter()
897 .map(|l| (l.dir, l.lane_type))
898 .collect::<Vec<_>>()
899 {
900 continue;
901 }
902
903 println!("Maybe merge {}", i.id);
904 diff_tags(&r1.osm_tags, &r2.osm_tags);
905 println!();
906 }
907}
908
909fn diff_tags(t1: &Tags, t2: &Tags) {
910 for (k, v1, v2) in t1.diff(t2) {
911 println!("- {} = \"{}\" vs \"{}\"", k, v1, v2);
912 }
913}
914
915fn find_large_intersections(app: &App) {
916 let mut seen = HashSet::new();
917 for t in app.primary.map.all_turns() {
918 if !seen.contains(&t.id.parent) && t.geom.length() > Distance::meters(50.0) {
919 println!("{} has a long turn", t.id.parent);
920 seen.insert(t.id.parent);
921 }
922 }
923}
924
925struct ScreenshotTest {
928 todo_maps: Vec<MapName>,
929 screenshot_done: bool,
930}
931
932impl ScreenshotTest {
933 fn new_state(
934 ctx: &mut EventCtx,
935 app: &mut App,
936 mut todo_maps: Vec<MapName>,
937 ) -> Box<dyn State<App>> {
938 app.change_color_scheme(ctx, ColorSchemeChoice::DayMode);
941 ctx.canvas.settings.min_zoom_for_detail = 0.0;
942 MapLoader::new_state(
943 ctx,
944 app,
945 todo_maps.pop().unwrap(),
946 Box::new(move |_, _| {
947 Transition::Replace(Box::new(ScreenshotTest {
948 todo_maps,
949 screenshot_done: false,
950 }))
951 }),
952 )
953 }
954}
955
956impl State<App> for ScreenshotTest {
957 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
958 if self.screenshot_done {
959 if self.todo_maps.is_empty() {
960 Transition::Pop
961 } else {
962 Transition::Replace(ScreenshotTest::new_state(
963 ctx,
964 app,
965 self.todo_maps.drain(..).collect(),
966 ))
967 }
968 } else {
969 self.screenshot_done = true;
970 let name = app.primary.map.get_name();
971 ctx.request_update(UpdateType::ScreenCaptureEverything {
972 dir: format!(
973 "screenshots/{}/{}/{}",
974 name.city.country, name.city.city, name.map
975 ),
976 zoom: 3.0,
977 dims: ctx.canvas.get_window_dims(),
978 });
979 Transition::Keep
981 }
982 }
983 fn draw(&self, _: &mut GfxCtx, _: &App) {}
984}
985
986fn draw_banned_turns(ctx: &mut EventCtx, app: &App) -> Drawable {
987 let mut batch = GeomBatch::new();
988 let map = &app.primary.map;
989 for i in map.all_intersections() {
990 let mut pairs: HashSet<(RoadID, RoadID)> = HashSet::new();
991 for l1 in i.get_incoming_lanes(map, PathConstraints::Car) {
993 for l2 in i.get_outgoing_lanes(map, PathConstraints::Car) {
994 pairs.insert((l1.road, l2.road));
995 }
996 }
997 for t in &i.turns {
998 let r1 = t.id.src.road;
999 let r2 = t.id.dst.road;
1000 pairs.remove(&(r1, r2));
1001 }
1002
1003 for (r1, r2) in pairs {
1004 if let Ok(pl) = PolyLine::new(vec![
1005 map.get_r(r1).center_pts.middle(),
1006 map.get_r(r2).center_pts.middle(),
1007 ]) {
1008 batch.push(
1009 Color::RED,
1010 pl.make_arrow(Distance::meters(1.0), ArrowCap::Triangle),
1011 );
1012 }
1013 }
1014 }
1015 ctx.upload(batch)
1016}
1017
1018fn draw_arterial_crosswalks(ctx: &mut EventCtx, app: &App) -> Drawable {
1019 let mut batch = GeomBatch::new();
1020 let map = &app.primary.map;
1021 for turn in map.all_turns() {
1022 if turn.is_crossing_arterial_intersection(map) {
1023 batch.push(
1024 Color::RED,
1025 turn.geom
1026 .make_arrow(Distance::meters(2.0), ArrowCap::Triangle),
1027 );
1028 }
1029 }
1030 ctx.upload(batch)
1031}
1032
1033fn draw_bad_intersections(ctx: &mut EventCtx, app: &App) -> Drawable {
1034 let mut batch = GeomBatch::new();
1035 for i in app.primary.map.all_intersections() {
1036 if i.polygon.get_outer_ring().doubles_back() {
1037 batch.push(Color::RED.alpha(0.8), i.polygon.clone());
1038 }
1039 }
1040 ctx.upload(batch)
1041}