a-b-street/abstreet · error
didn't map to exactly 1 crossing turn
Error message
{:?} didn't map to exactly 1 crossing turn: {:?} What it means
When converting an edited crosswalk movement into a turn for a permanent edit, the library expects exactly one turn at the intersection matching the movement. Zero or multiple matching turns indicate the intersection's turn topology doesn't correspond to the edited crosswalk, so the mapping is ambiguous and the edit is rejected.
Solutions
- Recreate the crosswalk edit on the current map so the movement maps to a single turn.
- Check the intersection's current turns (map.get_i(i).turns) and confirm the edited movement still exists exactly once.
- Remove the stale crosswalk from the edit if the intersection topology changed permanently.
Example fix
// before
bail!("{:?} didn't map to exactly 1 crossing turn: {:?}", movement, turn_ids);
// after: filter only crossing movements that exist
if turn_ids.len() == 1 {
crosswalks.insert(turn_ids.pop().unwrap(), turn_type);
} Defensive patterns
Strategy: validation
Validate before calling
let matching: Vec<_> = map.get_i(i).turns.iter()
.filter(|t| t.id.to_movement(&map) == Some(movement))
.collect();
if matching.len() != 1 { /* recreate the crosswalk edit */ } Try / catch
match edits.to_permanent(&mut map) {
Err(e) if e.to_string().contains("crossing turn") => recreate_crosswalk_edits(&map)?,
other => other?,
} Prevention
- Apply crosswalk edits only to the map version they were created against
- Re-verify crosswalks after any edit that changes intersection turns
- Prefer recording crosswalk edits from the live UI rather than editing JSON by hand
When it happens
Trigger: with_permanent on an edit containing crosswalks (EditIntersectionControl) whose movement matches 0 or >1 turns in map.get_i(i).turns — e.g. after permanent edits changed the intersection's turns, or movements that aren't crossing movements.
Common situations: Applying crosswalk edits to a map where the intersection was rebuilt (turns changed); edits created from a different map version; complex intersections where a movement matches multiple turns after topology edits.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- must_get_sidewalk broken by
- doesn't contain both and
- Breaking changes happened to map edits between v12 and v13…
- Stop sign has roads now, but from edits
- doesn't connect to
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/713cd560fb61877c.
Report an issue: GitHub.
Appendix: source
Thrown at map_model/src/edits/perma.rs:303
}
PermanentEditIntersectionControl::TrafficSignal(ts) => {
EditIntersectionControl::TrafficSignal(ts)
}
PermanentEditIntersectionControl::Closed => EditIntersectionControl::Closed,
};
let mut crosswalks = BTreeMap::new();
for (id, turn_type) in self.crosswalks {
let movement = MovementID::from_permanent(id, map)?;
// Find all TurnIDs that map to this MovementID
let mut turn_ids = Vec::new();
for turn in &map.get_i(i).turns {
if turn.id.to_movement(map) == movement {
turn_ids.push(turn.id);
}
}
if turn_ids.len() != 1 {
bail!(
"{:?} didn't map to exactly 1 crossing turn: {:?}",
movement,
turn_ids
);
}
crosswalks.insert(turn_ids.pop().unwrap(), turn_type);
}
Ok(EditIntersection {
control,
// TODO Express as GeoJSON
modal_filter: self.modal_filter.clone(),
crosswalks,
})
}
}
View on GitHub (pinned to 0964f29315)