game/debug/
mod.rs

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                            // TODO Oh no, we have to do path construction here :(
283                            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                    // TODO Loading screen doesn't actually display anything because of the rules
349                    // around hiding the first few draws
350                    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                    // Abuse this just to draw
371                    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                // TODO We should really recalculate current_selection when these change. Meh.
408                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    // TODO Case insensitive
521    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                            // Just abuse this to display the results
776                            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                            // Rendering the error message is breaking
799                            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                    // TODO The center looks really wrong for Volunteer Park and others, but I
816                    // think it's because they have many points along some edges.
817                    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        // Hack so info panels can be opened in DebugMode
856        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
872// Consider this a second pass to debug, after map_model/src/make/collapse_intersections.rs. Rules
873// developed here will make their way there.
874fn 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
925// Because of the slightly odd control flow needed to ask widgetry to ScreenCaptureEverything, a
926// separate state is the easiest way to automatically screenshot multiple maps.
927struct 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        // Taking screenshots messes with options and doesn't restore them after. It's expected
939        // whoever's taking screenshots (just Dustin so far) will just quit after taking them.
940        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            // TODO Sometimes this still gets stuck and needs a mouse wiggle for input event?
980            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        // Don't call out one-ways, so use incoming/outgoing roads, and just for cars.
992        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}