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

  1. Delete the stale ChangeLaneType command from the edit JSON (or recreate the edit against the current map).
  2. Regenerate the map so lane types match what the edit expects, then load the edit.
  3. 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

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


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)