game/edit/
validate.rs
1use std::collections::BTreeSet;
2
3use abstutil::Timer;
4use map_model::{connectivity, Direction, DrivingSide, EditCmd, Map, PathConstraints};
5use widgetry::tools::PopupMsg;
6use widgetry::{EventCtx, State};
7
8use crate::app::App;
9
10pub fn check_sidewalk_connectivity(
15 ctx: &mut EventCtx,
16 app: &mut App,
17 cmd: EditCmd,
18) -> Option<Box<dyn State<App>>> {
19 let orig_edits = app.primary.map.get_edits().clone();
20 let (_, disconnected_before) =
21 connectivity::find_scc(&app.primary.map, PathConstraints::Pedestrian);
22
23 let mut edits = orig_edits.clone();
24 edits.commands.push(cmd);
25 app.primary
26 .map
27 .try_apply_edits(edits, &mut Timer::throwaway());
28
29 let (_, disconnected_after) =
30 connectivity::find_scc(&app.primary.map, PathConstraints::Pedestrian);
31 app.primary
32 .map
33 .must_apply_edits(orig_edits, &mut Timer::throwaway());
34
35 let newly_disconnected = disconnected_after
36 .difference(&disconnected_before)
37 .collect::<Vec<_>>();
38 if newly_disconnected.is_empty() {
39 return None;
40 }
41
42 Some(PopupMsg::new_state(
46 ctx,
47 "Error",
48 vec![format!(
49 "Can't close this intersection; {} sidewalks disconnected",
50 newly_disconnected.len()
51 )],
52 ))
53}
54
55#[allow(unused)]
56pub fn check_blackholes(
58 ctx: &mut EventCtx,
59 app: &mut App,
60 cmd: EditCmd,
61) -> Option<Box<dyn State<App>>> {
62 let orig_edits = app.primary.map.get_edits().clone();
63 let mut driving_ok_originally = BTreeSet::new();
64 let mut biking_ok_originally = BTreeSet::new();
65 for l in app.primary.map.all_lanes() {
66 if !l.driving_blackhole {
67 driving_ok_originally.insert(l.id);
68 }
69 if !l.biking_blackhole {
70 biking_ok_originally.insert(l.id);
71 }
72 }
73
74 let mut edits = orig_edits.clone();
75 edits.commands.push(cmd);
76 app.primary
77 .map
78 .try_apply_edits(edits, &mut Timer::throwaway());
79
80 let mut newly_disconnected = BTreeSet::new();
81 for l in connectivity::find_scc(&app.primary.map, PathConstraints::Car).1 {
82 if driving_ok_originally.contains(&l) {
83 newly_disconnected.insert(l);
84 }
85 }
86 for l in connectivity::find_scc(&app.primary.map, PathConstraints::Bike).1 {
87 if biking_ok_originally.contains(&l) {
88 newly_disconnected.insert(l);
89 }
90 }
91 app.primary
92 .map
93 .must_apply_edits(orig_edits, &mut Timer::throwaway());
94
95 if newly_disconnected.is_empty() {
96 return None;
97 }
98
99 Some(PopupMsg::new_state(
100 ctx,
101 "Error",
102 vec![format!(
103 "{} lanes have been disconnected",
104 newly_disconnected.len()
105 )],
106 ))
107}
108
109pub fn fix_sidewalk_direction(map: &Map) -> Vec<EditCmd> {
112 let mut fixes = Vec::new();
113 for cmd in &map.get_edits().commands {
114 if let EditCmd::ChangeRoad { r, new, .. } = cmd {
115 let mut fixed = new.clone();
116 if fixed.lanes_ltr[0].lt.is_walkable() {
117 fixed.lanes_ltr[0].dir = if map.get_config().driving_side == DrivingSide::Right {
118 Direction::Back
119 } else {
120 Direction::Fwd
121 };
122 }
123 if fixed.lanes_ltr.len() > 1 {
124 let last = fixed.lanes_ltr.last_mut().unwrap();
125 if last.lt.is_walkable() {
126 last.dir = if map.get_config().driving_side == DrivingSide::Right {
127 Direction::Fwd
128 } else {
129 Direction::Back
130 };
131 }
132 }
133 if &fixed != new {
134 fixes.push(EditCmd::ChangeRoad {
135 r: *r,
136 old: new.clone(),
137 new: fixed,
138 });
139 }
140 }
141 }
142 fixes
143}