a-b-street/abstreet · error
lane type has changed
Error message
{:?} lane type has changed What it means
While upgrading old edit files, fix_old_lane_cmds replays a ChangeLaneType command only if the lane still has the same original type recorded in the command (obj.orig_lt). If the road's lane type has since changed (map regeneration, another edit, OSM update), the edit's precondition no longer holds, so upgrade aborts with this error instead of silently corrupting the lane.
Solutions
- Delete the stale ChangeLaneType command from the edit JSON (or recreate the edit against the current map).
- Regenerate the map so lane types match what the edit expects, then load the edit.
- If the new type is acceptable, hand-edit the edit file so orig_lt matches the current lane type.
Example fix
// before: loading stale edit
let edits = Edits::load_from_file(map, path);
// after: strip the broken command first
let mut v: serde_json::Value = serde_json::from_reader(file)?;
for cmd in v["commands"].as_array_mut().unwrap() {
cmd.as_object_mut().unwrap().remove("ChangeLaneType");
}
let edits = Edits::load_from_bytes(map, &serde_json::to_vec(&v)?); Defensive patterns
Strategy: try-catch
Validate before calling
// check the precondition before loading
let (r, idx) = cmd.id.lookup(map)?;
if map.get_r_edit(r).lanes_ltr[idx].lt != cmd.orig_lt { /* drop or repair cmd */ } Try / catch
match Edits::load_from_file(map, path) {
Ok(e) => apply(e),
Err(err) if err.to_string().contains("lane type has changed") => {
warn!("stale ChangeLaneType dropped: {}", err);
// re-create edit without that command
}
Err(err) => return Err(err),
} Prevention
- Regenerate edits whenever the basemap is re-imported from OSM.
- Apply edits promptly after creating them; don't hoard old edit files across map versions.
- Keep a record of which map name/version each edit file targets.
When it happens
Trigger: Calling Map Edits load (compat::upgrade) on a saved edit JSON containing ChangeLaneType whose `old`/orig_lt no longer equals road.lanes_ltr[idx].lt in the current map — e.g. the basemap was re-generated or the lane was edited before.
Common situations: Loading old saved edits after re-importing the map from OSM; applying edits made before another edit changed the lane type; sharing edit files between differently-versioned map builds.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- speed limit has changed
- access restrictions have changed
- 's road doesn't point to dst_i at all
- 's road already points to dst_i
- number of lanes in is ( fwd, back) now, but ( , ) in the…
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/de695d9905d4bc0a.
Report an issue: GitHub.
Appendix: source
Thrown at map_model/src/edits/compat.rs:225
// target a single lane in favor of a consolidated ChangeRoad.
fn fix_old_lane_cmds(value: &mut Value, map: &Map) -> Result<()> {
// TODO Can we assume map is in its original state? I don't think so... it may have edits
// applied, right?
let mut modified: BTreeMap<RoadID, EditRoad> = BTreeMap::new();
let mut commands = Vec::new();
for mut orig in value.as_object_mut().unwrap()["commands"]
.as_array_mut()
.unwrap()
.drain(..)
{
let cmd = orig.as_object_mut().unwrap();
if let Some(obj) = cmd.remove("ChangeLaneType") {
let obj: ChangeLaneType = serde_json::from_value(obj).unwrap();
let (r, idx) = obj.id.lookup(map)?;
let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
if road.lanes_ltr[idx].lt != obj.orig_lt {
bail!("{:?} lane type has changed", obj);
}
road.lanes_ltr[idx].lt = obj.lt;
} else if let Some(obj) = cmd.remove("ReverseLane") {
let obj: ReverseLane = serde_json::from_value(obj).unwrap();
let (r, idx) = obj.l.lookup(map)?;
let dst_i = map.find_i_by_osm_id(obj.dst_i)?;
let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
let edits_dir = if dst_i == map.get_r(r).dst_i {
Direction::Fwd
} else if dst_i == map.get_r(r).src_i {
Direction::Back
} else {
bail!("{:?}'s road doesn't point to dst_i at all", obj);
};
if road.lanes_ltr[idx].dir == edits_dir {
bail!("{:?}'s road already points to dst_i", obj);
}
road.lanes_ltr[idx].dir = edits_dir;View on GitHub (pinned to 0964f29315)