map_model/edits/
compat.rs

1use std::collections::BTreeMap;
2
3use anyhow::Result;
4use serde::Deserialize;
5use serde_json::Value;
6
7use abstio::{CityName, MapName};
8use geom::Speed;
9
10use crate::{
11    osm, AccessRestrictions, Direction, EditCmd, EditRoad, LaneSpec, LaneType, Map, OriginalRoad,
12    PermanentMapEdits, RoadID,
13};
14
15/// When the PermanentMapEdits format changes, add a transformation here to automatically convert
16/// edits written with the old format.
17///
18/// This problem is often solved with something like protocol buffers, but the resulting proto
19/// usually winds up with permanent legacy fields, unless the changes are purely additive. For
20/// example, protobufs wouldn't have helped with the fix_intersection_ids problem. Explicit
21/// transformation is easier!
22pub fn upgrade(mut value: Value, map: &Map) -> Result<PermanentMapEdits> {
23    // c46a74f10f4f1976a48aa8642ac11717d74b262c added an explicit version field. There are a few
24    // changes before that.
25    if value.get("version").is_none() {
26        // I don't remember the previous schema change before this. If someone files a bug and has
27        // an older file, can add support for it then.
28        fix_offset(&mut value);
29        fix_intersection_ids(&mut value);
30
31        value
32            .as_object_mut()
33            .unwrap()
34            .insert("version".to_string(), Value::Number(0.into()));
35    }
36    if value["version"] == Value::Number(0.into()) {
37        fix_road_direction(&mut value);
38        value
39            .as_object_mut()
40            .unwrap()
41            .insert("version".to_string(), Value::Number(1.into()));
42    }
43    if value["version"] == Value::Number(1.into()) {
44        fix_old_lane_cmds(&mut value, map)?;
45        value
46            .as_object_mut()
47            .unwrap()
48            .insert("version".to_string(), Value::Number(2.into()));
49    }
50    if value["version"] == Value::Number(2.into()) {
51        fix_merge_zones(&mut value);
52        value
53            .as_object_mut()
54            .unwrap()
55            .insert("version".to_string(), Value::Number(3.into()));
56    }
57    if value["version"] == Value::Number(3.into()) {
58        fix_map_name(&mut value);
59        value
60            .as_object_mut()
61            .unwrap()
62            .insert("version".to_string(), Value::Number(4.into()));
63    }
64    if value["version"] == Value::Number(4.into()) {
65        fix_phase_to_stage(&mut value);
66        value
67            .as_object_mut()
68            .unwrap()
69            .insert("version".to_string(), Value::Number(5.into()));
70    }
71    if value["version"] == Value::Number(5.into()) {
72        fix_adaptive_stages(&mut value);
73        value
74            .as_object_mut()
75            .unwrap()
76            .insert("version".to_string(), Value::Number(6.into()));
77    }
78    if value["version"] == Value::Number(6.into()) {
79        fix_plans(&mut value);
80        value
81            .as_object_mut()
82            .unwrap()
83            .insert("version".to_string(), Value::Number(7.into()));
84    }
85    if value["version"] == Value::Number(7.into()) {
86        fix_city_name(&mut value);
87        value
88            .as_object_mut()
89            .unwrap()
90            .insert("version".to_string(), Value::Number(8.into()));
91    }
92    if value["version"] == Value::Number(8.into()) {
93        fix_lane_widths(&mut value, map)?;
94        value
95            .as_object_mut()
96            .unwrap()
97            .insert("version".to_string(), Value::Number(9.into()));
98    }
99    if value["version"] == Value::Number(9.into()) {
100        fix_f64s(&mut value);
101        value
102            .as_object_mut()
103            .unwrap()
104            .insert("version".to_string(), Value::Number(10.into()));
105    }
106    if value["version"] == Value::Number(10.into()) {
107        remove_vehicle_caps(&mut value);
108        value
109            .as_object_mut()
110            .unwrap()
111            .insert("version".to_string(), Value::Number(11.into()));
112    }
113    if value["version"] == Value::Number(11.into()) {
114        fix_turn_restrictions(&mut value);
115        value
116            .as_object_mut()
117            .unwrap()
118            .insert("version".to_string(), Value::Number(12.into()));
119    }
120    if value["version"] == Value::Number(12.into()) {
121        bail!("Breaking changes happened to map edits between v12 and v13. Recreate your edits from scratch; sorry.");
122    }
123
124    abstutil::from_json(&value.to_string().into_bytes())
125}
126
127// Recursively walks the entire JSON object. Will call transform on all of the map objects. If the
128// callback returns true, won't recurse into that map.
129fn walk<F: Fn(&mut serde_json::Map<String, Value>) -> bool>(value: &mut Value, transform: &F) {
130    match value {
131        Value::Array(list) => {
132            for x in list {
133                walk(x, transform);
134            }
135        }
136        Value::Object(map) => {
137            if !(transform)(map) {
138                for x in map.values_mut() {
139                    walk(x, transform);
140                }
141            }
142        }
143        _ => {}
144    }
145}
146
147// eee179ce8a6c1e6133dc212b73c3f79b11603e82 added an offset_seconds field
148fn fix_offset(value: &mut Value) {
149    walk(value, &|map| {
150        if map.len() == 1 && map.contains_key("TrafficSignal") {
151            let ts = map
152                .get_mut("TrafficSignal")
153                .unwrap()
154                .as_object_mut()
155                .unwrap();
156            if ts.get("offset_seconds").is_none() {
157                ts.insert("offset_seconds".to_string(), Value::Number(0.into()));
158            }
159            true
160        } else {
161            false
162        }
163    })
164}
165
166// 11cefb118ab353d2e7fa5dceaab614a9b775e6ec changed { "osm_node_id": 123 } to just 123
167fn fix_intersection_ids(value: &mut Value) {
168    match value {
169        Value::Array(list) => {
170            for x in list {
171                fix_intersection_ids(x);
172            }
173        }
174        Value::Object(map) => {
175            if map.len() == 1 && map.contains_key("osm_node_id") {
176                *value = Value::Number(map["osm_node_id"].as_i64().unwrap().into());
177            } else {
178                for x in map.values_mut() {
179                    fix_intersection_ids(x);
180                }
181            }
182        }
183        _ => {}
184    }
185}
186
187// b137735e019adbe0f2a7372a579aa987f8496e19 changed direction from a boolean to an enum.
188fn fix_road_direction(value: &mut Value) {
189    walk(value, &|map| {
190        if map.contains_key("num_fwd") {
191            map.insert(
192                "dir".to_string(),
193                if map["fwd"].as_bool().unwrap() {
194                    "Fwd".into()
195                } else {
196                    "Back".into()
197                },
198            );
199            true
200        } else {
201            false
202        }
203    });
204}
205
206// b6ab06d51a3b22702b66db296ed4dfd27e8403a0 (and adjacent commits) removed some commands that
207// target a single lane in favor of a consolidated ChangeRoad.
208fn fix_old_lane_cmds(value: &mut Value, map: &Map) -> Result<()> {
209    // TODO Can we assume map is in its original state? I don't think so... it may have edits
210    // applied, right?
211
212    let mut modified: BTreeMap<RoadID, EditRoad> = BTreeMap::new();
213    let mut commands = Vec::new();
214    for mut orig in value.as_object_mut().unwrap()["commands"]
215        .as_array_mut()
216        .unwrap()
217        .drain(..)
218    {
219        let cmd = orig.as_object_mut().unwrap();
220        if let Some(obj) = cmd.remove("ChangeLaneType") {
221            let obj: ChangeLaneType = serde_json::from_value(obj).unwrap();
222            let (r, idx) = obj.id.lookup(map)?;
223            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
224            if road.lanes_ltr[idx].lt != obj.orig_lt {
225                bail!("{:?} lane type has changed", obj);
226            }
227            road.lanes_ltr[idx].lt = obj.lt;
228        } else if let Some(obj) = cmd.remove("ReverseLane") {
229            let obj: ReverseLane = serde_json::from_value(obj).unwrap();
230            let (r, idx) = obj.l.lookup(map)?;
231            let dst_i = map.find_i_by_osm_id(obj.dst_i)?;
232            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
233            let edits_dir = if dst_i == map.get_r(r).dst_i {
234                Direction::Fwd
235            } else if dst_i == map.get_r(r).src_i {
236                Direction::Back
237            } else {
238                bail!("{:?}'s road doesn't point to dst_i at all", obj);
239            };
240            if road.lanes_ltr[idx].dir == edits_dir {
241                bail!("{:?}'s road already points to dst_i", obj);
242            }
243            road.lanes_ltr[idx].dir = edits_dir;
244        } else if let Some(obj) = cmd.remove("ChangeSpeedLimit") {
245            let obj: ChangeSpeedLimit = serde_json::from_value(obj).unwrap();
246            let r = map.find_r_by_osm_id(obj.id)?;
247            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
248            if road.speed_limit != obj.old {
249                bail!("{:?} speed limit has changed", obj);
250            }
251            road.speed_limit = obj.new;
252        } else if let Some(obj) = cmd.remove("ChangeAccessRestrictions") {
253            let obj: ChangeAccessRestrictions = serde_json::from_value(obj).unwrap();
254            let r = map.find_r_by_osm_id(obj.id)?;
255            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
256            if road.access_restrictions != obj.old {
257                bail!("{:?} access restrictions have changed", obj);
258            }
259            road.access_restrictions = obj.new.clone();
260        } else {
261            commands.push(orig);
262        }
263    }
264
265    for (r, new) in modified {
266        let old = map.get_r_edit(r);
267        commands
268            .push(serde_json::to_value(EditCmd::ChangeRoad { r, old, new }.to_perma(map)).unwrap());
269    }
270    value.as_object_mut().unwrap()["commands"] = Value::Array(commands);
271    Ok(())
272}
273
274// a3af291b2966c89d63b719e41821705077d063d2 added a map-wide merge_zones field
275fn fix_merge_zones(value: &mut Value) {
276    let obj = value.as_object_mut().unwrap();
277    if !obj.contains_key("merge_zones") {
278        obj.insert("merge_zones".to_string(), Value::Bool(true));
279    }
280}
281
282// fef306489ba5e73735e0badad0172f3992d342db split map/city name into a dedicated struct
283fn fix_map_name(value: &mut Value) {
284    let root = value.as_object_mut().unwrap();
285    if let Value::String(ref name) = root["map_name"].clone() {
286        // At the time of this change, there likely aren't many people who have edits saved in
287        // other maps.
288        root.insert(
289            "map_name".to_string(),
290            serde_json::to_value(MapName::seattle(name)).unwrap(),
291        );
292    }
293}
294
295// 03fe9400c2ab98b8870e09562b1f35b91036f3cf renamed "phase" to "stage"
296fn fix_phase_to_stage(value: &mut Value) {
297    walk(value, &|map| {
298        if let Some(list) = map.remove("phases") {
299            map.insert("stages".to_string(), list);
300        }
301        if let Some(obj) = map.remove("phase_type") {
302            map.insert("stage_type".to_string(), obj);
303        }
304        false
305    });
306}
307
308// 34e8b0536a4517c68b0e16e5d55cb5e22dae37d8 remove adaptive signal stages.
309fn fix_adaptive_stages(value: &mut Value) {
310    walk(value, &|map| {
311        if let Some(seconds) = map.remove("Adaptive") {
312            // The old adaptive policy would repeat the entire stage if there was any demand at
313            // all, so this isn't quite equivalent, since it only doubles the original time at
314            // most. This adaptive policy never made any sense, so capturing its behavior more
315            // clearly here isn't really worth it.
316            let minimum = seconds.clone();
317            let delay = Value::Number(1.into());
318            let additional = seconds;
319            map.insert(
320                "Variable".to_string(),
321                Value::Array(vec![minimum, delay, additional]),
322            );
323        }
324        false
325    });
326}
327
328// e08d76c8ba51d1ca8045e7692195b5f6245150c4 added traffic signal plans.
329fn fix_plans(value: &mut Value) {
330    walk(value, &|map| {
331        if map.len() == 1 && map.contains_key("TrafficSignal") {
332            let ts = map
333                .get_mut("TrafficSignal")
334                .unwrap()
335                .as_object_mut()
336                .unwrap();
337            let mut plan = serde_json::Map::new();
338            plan.insert("start_time_seconds".to_string(), Value::Number(0.into()));
339            plan.insert("stages".to_string(), ts.remove("stages").unwrap());
340            plan.insert(
341                "offset_seconds".to_string(),
342                ts.remove("offset_seconds").unwrap(),
343            );
344            ts.insert("plans".to_string(), Value::Array(vec![Value::Object(plan)]));
345            true
346        } else {
347            false
348        }
349    })
350}
351
352// 39f5d50fcd981d62792562429003ce9725c17277 split city name into a dedicated struct
353fn fix_city_name(value: &mut Value) {
354    let root = value.as_object_mut().unwrap();
355    let map_name = root["map_name"].as_object_mut().unwrap();
356    if let Value::String(ref name) = map_name["city"].clone() {
357        // At the time of this change, there are only a few maps that somebody likely had edits
358        // for.
359        let country = match name.as_ref() {
360            "salzburg" => "at",
361            "montreal" => "ca",
362            "berlin" => "de",
363            "paris" => "fr",
364            "leeds" | "london" => "gb",
365            "tel_aviv" => "il",
366            "krakow" | "warsaw" => "pl",
367            _ => "us",
368        };
369        map_name.insert(
370            "city".to_string(),
371            serde_json::to_value(CityName::new(country, name)).unwrap(),
372        );
373    }
374}
375
376// 0e09c1decfece370eff72d3433ae00b4aff3332f added lane width to EditRoad.
377fn fix_lane_widths(value: &mut Value, map: &Map) -> Result<()> {
378    for orig in value.as_object_mut().unwrap()["commands"]
379        .as_array_mut()
380        .unwrap()
381    {
382        let cmd = orig.as_object_mut().unwrap();
383        if let Some(cmd) = cmd.get_mut("ChangeRoad") {
384            let road_id: OriginalRoad = serde_json::from_value(cmd["r"].clone()).unwrap();
385            let road = map.get_r(map.find_r_by_osm_id(road_id)?);
386            let cmd = cmd.as_object_mut().unwrap();
387
388            for key in ["old", "new"] {
389                let mut lanes_ltr = Vec::new();
390                for (idx, mut pair) in cmd[key]["lanes_ltr"]
391                    .as_array_mut()
392                    .unwrap()
393                    .drain(..)
394                    .enumerate()
395                {
396                    let pair = pair.as_array_mut().unwrap();
397                    let lt: LaneType = serde_json::from_value(pair[0].clone()).unwrap();
398                    let dir: Direction = serde_json::from_value(pair[1].clone()).unwrap();
399                    lanes_ltr.push(LaneSpec {
400                        lt,
401                        dir,
402                        // Before this commit, lane widths weren't modifiable, so this lookup works
403                        // for both "old" and "new".
404                        width: road.lanes[idx].width,
405                        allowed_turns: Default::default(),
406                    });
407                }
408                cmd[key]["lanes_ltr"] = serde_json::to_value(lanes_ltr).unwrap();
409            }
410        }
411    }
412    Ok(())
413}
414
415// f9c503ba4422ff51b609a7dc68855e8049bc9fd3 started encoding all f64's as rounded integers
416fn fix_f64s(value: &mut Value) {
417    walk(value, &|map| {
418        // There's one Distance and one Speed field to re-encode
419        for key in ["width", "speed_limit"] {
420            if let Some(value) = map.get_mut(key) {
421                if let Value::Number(num) = value {
422                    if num.is_f64() {
423                        let encoded = (num.as_f64().unwrap() * 10_000.0) as i32;
424                        *value = Value::Number(encoded.into());
425                    }
426                }
427            }
428        }
429        // ChangeRouteSchedule has an 'old' and 'new' Vec<Time>
430        if map.contains_key("osm_rel_id") {
431            for key in ["old", "new"] {
432                for value in map.get_mut(key).unwrap().as_array_mut().unwrap() {
433                    if let Value::Number(num) = value {
434                        if num.is_f64() {
435                            let encoded = (num.as_f64().unwrap() * 10_000.0) as i32;
436                            *value = Value::Number(encoded.into());
437                        }
438                    }
439                }
440            }
441        }
442
443        false
444    })
445}
446
447// a0dcc255c3212c491e1fa71546a8115fe319312e removed congestion capping
448fn remove_vehicle_caps(value: &mut Value) {
449    walk(value, &|map| {
450        map.remove("cap_vehicles_per_hour");
451        false
452    });
453}
454
455// 6af258636f926a12650063abf243a0b87567b6e0 (well, a bit earlier) added turn restrictions to
456// LaneSpecs
457fn fix_turn_restrictions(value: &mut Value) {
458    for orig in value.as_object_mut().unwrap()["commands"]
459        .as_array_mut()
460        .unwrap()
461    {
462        let cmd = orig.as_object_mut().unwrap();
463        if let Some(cmd) = cmd.get_mut("ChangeRoad") {
464            let cmd = cmd.as_object_mut().unwrap();
465            for key in ["old", "new"] {
466                for spec in cmd[key]["lanes_ltr"].as_array_mut().unwrap() {
467                    // We could try to parse OSM tags and calculate this properly, but this field
468                    // isn't used in A/B Street yet at all. So just fill out a blank list.
469                    spec.as_object_mut()
470                        .unwrap()
471                        .insert("turn_restrictions".to_string(), Value::Array(Vec::new()));
472                }
473            }
474        }
475    }
476}
477
478// These're old structs used in fix_old_lane_cmds.
479#[derive(Debug, Deserialize)]
480struct OriginalLane {
481    parent: OriginalRoad,
482    num_fwd: usize,
483    num_back: usize,
484    dir: Direction,
485    idx: usize,
486}
487#[derive(Debug, Deserialize)]
488struct ChangeLaneType {
489    id: OriginalLane,
490    lt: LaneType,
491    orig_lt: LaneType,
492}
493#[derive(Debug, Deserialize)]
494struct ReverseLane {
495    l: OriginalLane,
496    // New intended dst_i
497    dst_i: osm::NodeID,
498}
499#[derive(Debug, Deserialize)]
500struct ChangeSpeedLimit {
501    id: OriginalRoad,
502    new: Speed,
503    old: Speed,
504}
505#[derive(Debug, Deserialize)]
506struct ChangeAccessRestrictions {
507    id: OriginalRoad,
508    new: AccessRestrictions,
509    old: AccessRestrictions,
510}
511
512impl OriginalLane {
513    fn lookup(&self, map: &Map) -> Result<(RoadID, usize)> {
514        let r = map.get_r(map.find_r_by_osm_id(self.parent)?);
515        let current_fwd = r.children_forwards();
516        let current_back = r.children_backwards();
517        if current_fwd.len() != self.num_fwd || current_back.len() != self.num_back {
518            bail!(
519                "number of lanes in {} is ({} fwd, {} back) now, but ({}, {}) in the edits",
520                r.orig_id,
521                current_fwd.len(),
522                current_back.len(),
523                self.num_fwd,
524                self.num_back
525            );
526        }
527        let l = if self.dir == Direction::Fwd {
528            current_fwd[self.idx].0
529        } else {
530            current_back[self.idx].0
531        };
532        Ok((r.id, l.offset))
533    }
534}