{"record":{"id":"713cd560fb61877c","repo":"a-b-street/abstreet","slug":"didn-t-map-to-exactly-1-crossing-turn","errorCode":null,"errorMessage":"{:?} didn't map to exactly 1 crossing turn: {:?}","messagePattern":"(.+?) didn't map to exactly 1 crossing turn: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"map_model/src/edits/perma.rs","lineNumber":303,"sourceCode":"            }\n            PermanentEditIntersectionControl::TrafficSignal(ts) => {\n                EditIntersectionControl::TrafficSignal(ts)\n            }\n            PermanentEditIntersectionControl::Closed => EditIntersectionControl::Closed,\n        };\n\n        let mut crosswalks = BTreeMap::new();\n        for (id, turn_type) in self.crosswalks {\n            let movement = MovementID::from_permanent(id, map)?;\n            // Find all TurnIDs that map to this MovementID\n            let mut turn_ids = Vec::new();\n            for turn in &map.get_i(i).turns {\n                if turn.id.to_movement(map) == movement {\n                    turn_ids.push(turn.id);\n                }\n            }\n            if turn_ids.len() != 1 {\n                bail!(\n                    \"{:?} didn't map to exactly 1 crossing turn: {:?}\",\n                    movement,\n                    turn_ids\n                );\n            }\n            crosswalks.insert(turn_ids.pop().unwrap(), turn_type);\n        }\n\n        Ok(EditIntersection {\n            control,\n            // TODO Express as GeoJSON\n            modal_filter: self.modal_filter.clone(),\n            crosswalks,\n        })\n    }\n}\n","sourceCodeStart":285,"sourceCodeEnd":320,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/map_model/src/edits/perma.rs#L285-L320","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nbail!(\"{:?} didn't map to exactly 1 crossing turn: {:?}\", movement, turn_ids);\n// after: filter only crossing movements that exist\nif turn_ids.len() == 1 {\n    crosswalks.insert(turn_ids.pop().unwrap(), turn_type);\n}","handlingStrategy":"validation","validationCode":"let matching: Vec<_> = map.get_i(i).turns.iter()\n    .filter(|t| t.id.to_movement(&map) == Some(movement))\n    .collect();\nif matching.len() != 1 { /* recreate the crosswalk edit */ }","typeGuard":null,"tryCatchPattern":"match edits.to_permanent(&mut map) {\n    Err(e) if e.to_string().contains(\"crossing turn\") => recreate_crosswalk_edits(&map)?,\n    other => other?,\n}","preventionTips":["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"],"tags":["map-edits","crosswalk","turn-mapping"],"backgroundTag":"entity-not-found","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}