1use std::collections::{BTreeMap, BTreeSet};
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9
10use abstutil::Timer;
11use geom::{Speed, Time};
12use osm2streets::{get_lane_specs_ltr, RestrictionType};
13
14pub use self::perma::PermanentMapEdits;
15use crate::{
16 AccessRestrictions, ControlStopSign, ControlTrafficSignal, Crossing, DiagonalFilter,
17 IntersectionControl, IntersectionID, LaneID, LaneSpec, Map, MapConfig, ParkingLotID, Road,
18 RoadFilter, RoadID, TransitRouteID, TurnID, TurnType,
19};
20
21mod apply;
22mod compat;
23mod perma;
24pub mod perma_traffic_signal;
25
26#[derive(Debug, Clone, PartialEq)]
29pub struct MapEdits {
30 pub edits_name: String,
31 pub commands: Vec<EditCmd>,
34
35 pub original_roads: BTreeMap<RoadID, EditRoad>,
37 pub original_intersections: BTreeMap<IntersectionID, EditIntersection>,
38 pub changed_routes: BTreeSet<TransitRouteID>,
39
40 pub proposal_description: Vec<String>,
43 pub proposal_link: Option<String>,
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub enum EditCmd {
48 ChangeRoad {
49 r: RoadID,
50 old: EditRoad,
51 new: EditRoad,
52 },
53 ChangeIntersection {
54 i: IntersectionID,
55 new: EditIntersection,
56 old: EditIntersection,
57 },
58 ChangeRouteSchedule {
59 id: TransitRouteID,
60 old: Vec<Time>,
61 new: Vec<Time>,
62 },
63}
64
65pub struct EditEffects {
66 pub changed_roads: BTreeSet<RoadID>,
67 pub deleted_lanes: BTreeSet<LaneID>,
68 pub changed_intersections: BTreeSet<IntersectionID>,
69 pub added_turns: BTreeSet<TurnID>,
71 pub deleted_turns: BTreeSet<TurnID>,
72 pub changed_parking_lots: BTreeSet<ParkingLotID>,
73 modified_lanes: BTreeSet<LaneID>,
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct EditRoad {
78 pub lanes_ltr: Vec<LaneSpec>,
79 pub speed_limit: Speed,
80 pub access_restrictions: AccessRestrictions,
81 pub modal_filter: Option<RoadFilter>,
82 pub crossings: Vec<Crossing>,
83 pub turn_restrictions: Vec<(RestrictionType, RoadID)>,
84 pub complicated_turn_restrictions: Vec<(RoadID, RoadID)>,
85}
86
87#[derive(Debug, Clone, PartialEq)]
88pub struct EditIntersection {
89 pub control: EditIntersectionControl,
90 pub modal_filter: Option<DiagonalFilter>,
91 pub crosswalks: BTreeMap<TurnID, TurnType>,
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum EditIntersectionControl {
98 StopSign(ControlStopSign),
99 TrafficSignal(perma_traffic_signal::TrafficSignal),
102 Closed,
103}
104
105impl EditRoad {
106 pub fn get_orig_from_osm(r: &Road, cfg: &MapConfig) -> EditRoad {
107 EditRoad {
108 lanes_ltr: get_lane_specs_ltr(&r.osm_tags, cfg),
109 speed_limit: r.speed_limit_from_osm(),
110 access_restrictions: r.access_restrictions_from_osm(),
111 modal_filter: None,
113 crossings: Vec::new(),
115 turn_restrictions: Vec::new(),
120 complicated_turn_restrictions: Vec::new(),
121 }
122 }
123
124 fn diff(&self, other: &EditRoad) -> Vec<String> {
125 #![allow(clippy::comparison_chain)]
126 let mut lt = 0;
127 let mut dir = 0;
128 let mut width = 0;
129 for (spec1, spec2) in self.lanes_ltr.iter().zip(other.lanes_ltr.iter()) {
130 if spec1.lt != spec2.lt {
131 lt += 1;
132 }
133 if spec1.dir != spec2.dir {
134 dir += 1;
135 }
136 if spec1.width != spec2.width {
137 width += 1;
138 }
139 }
140
141 let mut changes = Vec::new();
142 if lt == 1 {
143 changes.push("1 lane type".to_string());
144 } else if lt > 1 {
145 changes.push(format!("{} lane types", lt));
146 }
147 if dir == 1 {
148 changes.push("1 lane reversal".to_string());
149 } else if dir > 1 {
150 changes.push(format!("{} lane reversals", dir));
151 }
152 if width == 1 {
153 changes.push("1 lane width".to_string());
154 } else {
155 changes.push(format!("{} lane widths", width));
156 }
157 if self.speed_limit != other.speed_limit {
158 changes.push("speed limit".to_string());
159 }
160 if self.access_restrictions != other.access_restrictions {
161 changes.push("access restrictions".to_string());
162 }
163 if self.modal_filter != other.modal_filter {
164 changes.push("modal filter".to_string());
165 }
166 if self.crossings != other.crossings {
167 changes.push("crossings".to_string());
168 }
169 changes
170 }
171}
172
173impl EditIntersection {
174 fn diff(&self, other: &EditIntersection) -> Vec<String> {
175 let mut changes = Vec::new();
176 if self.control != other.control {
178 changes.push("control type".to_string());
179 }
180 if self.crosswalks != other.crosswalks {
181 changes.push("crosswalks".to_string());
182 }
183 if self.modal_filter != other.modal_filter {
184 changes.push("modal filter".to_string());
185 }
186 changes
187 }
188}
189
190impl MapEdits {
191 pub(crate) fn new() -> MapEdits {
192 MapEdits {
193 edits_name: "TODO temporary".to_string(),
194 proposal_description: Vec::new(),
195 proposal_link: None,
196 commands: Vec::new(),
197
198 original_roads: BTreeMap::new(),
199 original_intersections: BTreeMap::new(),
200 changed_routes: BTreeSet::new(),
201 }
202 }
203
204 pub fn load_from_file(map: &Map, path: String, timer: &mut Timer) -> Result<MapEdits> {
208 let perma = match abstio::maybe_read_json::<PermanentMapEdits>(path.clone(), timer) {
209 Ok(perma) => perma,
210 Err(_) => {
211 let bytes = abstio::slurp_file(path)?;
213 let value = serde_json::from_slice(&bytes)?;
214 compat::upgrade(value, map)?
215 }
216 };
217
218 if map.get_name().city != perma.map_name.city {
222 bail!(
223 "Edits are for {:?}, but this map is {:?}",
224 perma.map_name.city,
225 map.get_name().city
226 );
227 }
228
229 let edits = perma.into_edits_permissive(map);
230 if edits.commands.is_empty() {
231 bail!("None of the edits apply to this map");
232 }
233 Ok(edits)
234 }
235
236 pub fn load_from_bytes(map: &Map, bytes: Vec<u8>) -> Result<MapEdits> {
240 let perma = match abstutil::from_json::<PermanentMapEdits>(&bytes) {
241 Ok(perma) => perma,
242 Err(_) => {
243 let contents = std::str::from_utf8(&bytes)?;
245 let value = serde_json::from_str(contents)?;
246 compat::upgrade(value, map)?
247 }
248 };
249 let edits = perma.into_edits_permissive(map);
250 if edits.commands.is_empty() {
251 bail!("None of the edits apply to this map");
252 }
253 Ok(edits)
254 }
255
256 fn save(&self, map: &Map) {
257 if self.edits_name.starts_with("Untitled Proposal") && self.commands.is_empty() {
259 return;
260 }
261
262 abstio::write_json(
263 abstio::path_edits(map.get_name(), &self.edits_name),
264 &self.to_permanent(map),
265 );
266 }
267
268 fn update_derived(&mut self, map: &Map) {
269 self.original_roads.clear();
270 self.original_intersections.clear();
271 self.changed_routes.clear();
272
273 for cmd in &self.commands {
274 match cmd {
275 EditCmd::ChangeRoad { r, ref old, .. } => {
276 if !self.original_roads.contains_key(r) {
277 self.original_roads.insert(*r, old.clone());
278 }
279 }
280 EditCmd::ChangeIntersection { i, ref old, .. } => {
281 if !self.original_intersections.contains_key(i) {
282 self.original_intersections.insert(*i, old.clone());
283 }
284 }
285 EditCmd::ChangeRouteSchedule { id, .. } => {
286 self.changed_routes.insert(*id);
287 }
288 }
289 }
290
291 self.original_roads
292 .retain(|r, orig| map.get_r_edit(*r) != orig.clone());
293 self.original_intersections
294 .retain(|i, orig| map.get_i_edit(*i) != orig.clone());
295 self.changed_routes.retain(|br| {
296 let r = map.get_tr(*br);
297 r.spawn_times != r.orig_spawn_times
298 });
299 }
300
301 pub fn compress(&mut self, map: &Map) {
303 for (r, old) in &self.original_roads {
304 self.commands.push(EditCmd::ChangeRoad {
305 r: *r,
306 old: old.clone(),
307 new: map.get_r_edit(*r),
308 });
309 }
310 for (i, old) in &self.original_intersections {
311 self.commands.push(EditCmd::ChangeIntersection {
312 i: *i,
313 old: old.clone(),
314 new: map.get_i_edit(*i),
315 });
316 }
317 for r in &self.changed_routes {
318 let r = map.get_tr(*r);
319 self.commands.push(EditCmd::ChangeRouteSchedule {
320 id: r.id,
321 new: r.spawn_times.clone(),
322 old: r.orig_spawn_times.clone(),
323 });
324 }
325 }
326
327 pub fn changed_lanes(&self, map: &Map) -> (BTreeSet<LaneID>, BTreeSet<RoadID>) {
330 let mut lanes = BTreeSet::new();
331 let mut roads = BTreeSet::new();
332 for (r, orig) in &self.original_roads {
333 let r = map.get_r(*r);
334 if r.speed_limit != orig.speed_limit
336 || r.access_restrictions != orig.access_restrictions
337 || r.modal_filter != orig.modal_filter
338 || r.crossings != orig.crossings
339 || r.lanes.len() != orig.lanes_ltr.len()
342 {
343 roads.insert(r.id);
344 } else {
345 for (l, spec) in r.lanes.iter().zip(orig.lanes_ltr.iter()) {
346 if l.dir != spec.dir || l.lane_type != spec.lt || l.width != spec.width {
347 lanes.insert(l.id);
348 }
349 }
350 }
351 }
352 (lanes, roads)
353 }
354
355 pub fn get_checksum(&self, map: &Map) -> String {
357 let bytes = abstutil::to_json(&self.to_permanent(map));
358 let mut context = md5::Context::new();
359 context.consume(&bytes);
360 format!("{:x}", context.compute())
361 }
362
363 pub fn get_title(&self) -> &str {
366 if self.proposal_description.is_empty() {
367 &self.edits_name
368 } else {
369 &self.proposal_description[0]
370 }
371 }
372
373 pub fn is_crossing_modified(&self, r: RoadID, crossing: &Crossing) -> bool {
374 if let Some(orig) = self.original_roads.get(&r) {
375 !orig.crossings.contains(crossing)
376 } else {
377 false
379 }
380 }
381 pub fn is_filter_modified(&self, r: RoadID, filter: &RoadFilter) -> bool {
382 if let Some(orig) = self.original_roads.get(&r) {
383 orig.modal_filter != Some(filter.clone())
384 } else {
385 false
386 }
387 }
388 pub fn is_diagonal_filter_modified(&self, i: IntersectionID, filter: &DiagonalFilter) -> bool {
389 if let Some(orig) = self.original_intersections.get(&i) {
390 orig.modal_filter != Some(filter.clone())
391 } else {
392 false
393 }
394 }
395}
396
397impl Default for MapEdits {
398 fn default() -> MapEdits {
399 MapEdits::new()
400 }
401}
402
403impl EditCmd {
404 pub fn describe(&self, map: &Map) -> (String, Vec<String>) {
406 let mut details = Vec::new();
407 let summary = match self {
408 EditCmd::ChangeRoad { r, old, new } => {
409 details = new.diff(old);
410 format!("road #{}", r.0)
411 }
412 EditCmd::ChangeIntersection { i, old, new } => {
413 details = new.diff(old);
414 format!("intersection #{}", i.0)
415 }
416 EditCmd::ChangeRouteSchedule { id, .. } => {
417 format!("reschedule route {}", map.get_tr(*id).short_name)
418 }
419 };
420 (summary, details)
421 }
422}
423
424impl Map {
425 pub fn new_edits(&self) -> MapEdits {
426 let mut edits = MapEdits::new();
427
428 let mut i = 1;
430 loop {
431 let name = format!("Untitled Proposal {}", i);
432 if !abstio::file_exists(abstio::path_edits(&self.name, &name)) {
433 edits.edits_name = name;
434 return edits;
435 }
436 i += 1;
437 }
438 }
439
440 pub fn get_edits(&self) -> &MapEdits {
441 &self.edits
442 }
443
444 pub fn unsaved_edits(&self) -> bool {
445 self.edits.edits_name.starts_with("Untitled Proposal") && !self.edits.commands.is_empty()
446 }
447
448 pub fn get_r_edit(&self, r: RoadID) -> EditRoad {
449 let r = self.get_r(r);
450 EditRoad {
451 lanes_ltr: r.lane_specs(),
452 speed_limit: r.speed_limit,
453 access_restrictions: r.access_restrictions.clone(),
454 modal_filter: r.modal_filter.clone(),
455 crossings: r.crossings.clone(),
456 turn_restrictions: r.turn_restrictions.clone(),
457 complicated_turn_restrictions: r.complicated_turn_restrictions.clone(),
458 }
459 }
460
461 pub fn edit_road_cmd<F: FnOnce(&mut EditRoad)>(&self, r: RoadID, f: F) -> EditCmd {
462 let old = self.get_r_edit(r);
463 let mut new = old.clone();
464 f(&mut new);
465 EditCmd::ChangeRoad { r, old, new }
466 }
467
468 pub fn get_i_edit(&self, i: IntersectionID) -> EditIntersection {
469 let i = self.get_i(i);
470 let control = match i.control {
471 IntersectionControl::Signed | IntersectionControl::Uncontrolled => {
472 EditIntersectionControl::StopSign(self.get_stop_sign(i.id).clone())
473 }
474 IntersectionControl::Signalled => {
475 EditIntersectionControl::TrafficSignal(self.get_traffic_signal(i.id).export(self))
476 }
477 IntersectionControl::Construction => EditIntersectionControl::Closed,
478 };
479 let mut crosswalks = BTreeMap::new();
480 for turn in &i.turns {
481 if turn.turn_type.pedestrian_crossing() {
482 crosswalks.insert(turn.id, turn.turn_type);
483 }
484 }
485 EditIntersection {
486 control,
487 modal_filter: i.modal_filter.clone(),
488 crosswalks,
489 }
490 }
491
492 pub fn edit_intersection_cmd<F: FnOnce(&mut EditIntersection)>(
493 &self,
494 i: IntersectionID,
495 f: F,
496 ) -> EditCmd {
497 let old = self.get_i_edit(i);
498 let mut new = old.clone();
499 f(&mut new);
500 EditCmd::ChangeIntersection { i, old, new }
501 }
502
503 pub fn save_edits(&self) {
504 let mut edits = self.edits.clone();
507 edits.commands.clear();
508 edits.compress(self);
509 edits.save(self);
510 }
511
512 pub fn incremental_edit_traffic_signal(&mut self, signal: ControlTrafficSignal) {
515 assert_eq!(
516 self.get_i(signal.id).control,
517 IntersectionControl::Signalled
518 );
519 self.traffic_signals.insert(signal.id, signal);
520 }
521
522 pub fn get_edits_change_key(&self) -> usize {
524 self.edits_generation
525 }
526}