a-b-street/abstreet · error

number of lanes in is ( fwd, back) now, but ( , ) in the…

Error message

number of lanes in {} is ({} fwd, {} back) now, but ({}, {}) in the edits

What it means

OriginalLane::lookup resolves a lane reference saved in an old edit by OSM road ID plus the recorded counts of forward/backward lanes. If the current road's actual lane counts differ from the edit's snapshot, the lane can no longer be identified unambiguously, so lookup fails.

Solutions

  1. Regenerate the map from the OSM snapshot the edits were created against.
  2. Update the edit file's num_fwd/num_back values to match the current lane counts.
  3. Remove commands referencing the changed road and re-create them in the app.

Example fix

// before (edit JSON)
{"parent": 17, "num_fwd": 2, "num_back": 1, "idx": 0}
// after: match current map (3 fwd, 1 back)
{"parent": 17, "num_fwd": 3, "num_back": 1, "idx": 0}
Defensive patterns

Strategy: validation

Validate before calling

// check lane counts on the parent road before resolving the lane
let r = map.get_r(map.find_r_by_osm_id(orig.parent)?);
let ok = r.children_forwards().len() == orig.num_fwd
    && r.children_backwards().len() == orig.num_back;

Prevention

When it happens

Trigger: Upgrading/loading edits that reference lanes whose parent road now has a different number of fwd/back lanes (current children_forwards/backwards lengths != num_fwd/num_back), typically after map regeneration from changed OSM data.

Common situations: OSM edits added/removed lanes on a road; loading edits from an older map build; lane counts changed by prior edits baked into the basemap.

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/c70b92613a38e2e9. Report an issue: GitHub.

Appendix: source

Thrown at map_model/src/edits/compat.rs:518

struct ChangeSpeedLimit {
    id: OriginalRoad,
    new: Speed,
    old: Speed,
}
#[derive(Debug, Deserialize)]
struct ChangeAccessRestrictions {
    id: OriginalRoad,
    new: AccessRestrictions,
    old: AccessRestrictions,
}

impl OriginalLane {
    fn lookup(&self, map: &Map) -> Result<(RoadID, usize)> {
        let r = map.get_r(map.find_r_by_osm_id(self.parent)?);
        let current_fwd = r.children_forwards();
        let current_back = r.children_backwards();
        if current_fwd.len() != self.num_fwd || current_back.len() != self.num_back {
            bail!(
                "number of lanes in {} is ({} fwd, {} back) now, but ({}, {}) in the edits",
                r.orig_id,
                current_fwd.len(),
                current_back.len(),
                self.num_fwd,
                self.num_back
            );
        }
        let l = if self.dir == Direction::Fwd {
            current_fwd[self.idx].0
        } else {
            current_back[self.idx].0
        };
        Ok((r.id, l.offset))
    }
}

View on GitHub (pinned to 0964f29315)