a-b-street/abstreet · error

This intersection doesn't have major/minor roads; they're…

Error message

This intersection doesn't have major/minor roads; they're all the same rank.

What it means

Within adjust_major_minor_timing, after ranking each road by get_detailed_rank, if all roads at the intersection share a single distinct rank there is no 'major' versus 'minor' split to apply, so the method bails.

Solutions

  1. Only call this method on intersections with a genuine rank difference; check ranks beforehand
  2. Reclassify the major road in OSM or map edits so it outranks the others
  3. Use a uniform-timing transformation (directly setting stage durations) for equal-rank intersections
  4. Skip these intersections and log them for manual review

Example fix

// before
signal.adjust_major_minor_timing(major, minor, map)?;
// after
let ranks: BTreeSet<usize> = map.get_i(signal.id).roads.iter()
    .map(|r| map.get_r(*r).get_detailed_rank()).collect();
if ranks.len() > 1 {
    signal.adjust_major_minor_timing(major, minor, map)?;
}
Defensive patterns

Strategy: validation

Validate before calling

let ranks: BTreeSet<usize> = map.get_i(signal.id).roads.iter()
    .map(|r| map.get_r(*r).get_detailed_rank()).collect();
if ranks.len() < 2 {
    return Err(anyhow!("no major/minor split at {}", signal.id));
}

Type guard

fn has_rank_split(map: &Map, signal: &TrafficSignal) -> bool {
    let ranks: BTreeSet<usize> = map.get_i(signal.id).roads.iter()
        .map(|r| map.get_r(*r).get_detailed_rank()).collect();
    ranks.len() > 1
}

Try / catch

if has_rank_split(map, signal) {
    signal.adjust_major_minor_timing(major, minor, map)?;
} else {
    apply_uniform_timing(signal, major + minor);
}

Prevention

When it happens

Trigger: Calling adjust_major_minor_timing on an intersection whose roads all have identical rank (all residential, or all same class) — the sorted, deduped ranks vector has length 1.

Common situations: Applying major/minor retiming to an all-minor intersection; map data where road classification changed so the former major road was downgraded to match its neighbors.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/b5e1a51790aa35ce. Report an issue: GitHub.

Appendix: source

Thrown at map_model/src/objects/traffic_signals.rs:227

        &mut self,
        major: Duration,
        minor: Duration,
        map: &Map,
    ) -> Result<()> {
        if self.stages.len() != 2 {
            bail!("This intersection doesn't have 2 stages.");
        }

        // What's the rank of each road?
        let mut rank_per_road: BTreeMap<RoadID, usize> = BTreeMap::new();
        for r in &map.get_i(self.id).roads {
            rank_per_road.insert(*r, map.get_r(*r).get_detailed_rank());
        }
        let mut ranks: Vec<usize> = rank_per_road.values().cloned().collect();
        ranks.sort_unstable();
        ranks.dedup();
        if ranks.len() == 1 {
            bail!("This intersection doesn't have major/minor roads; they're all the same rank.");
        }
        let highest_rank = ranks.pop().unwrap();

        // Try to apply the transformation
        let orig = self.clone();
        for stage in &mut self.stages {
            match stage.stage_type {
                StageType::Fixed(_) => {}
                _ => bail!("This intersection doesn't use fixed timing."),
            }
            // Ignoring crosswalks, do any of the turns come from a major road?
            if stage
                .protected_movements
                .iter()
                .any(|m| !m.crosswalk && highest_rank == rank_per_road[&m.from.road])
            {
                stage.stage_type = StageType::Fixed(major);
            } else {

View on GitHub (pinned to 0964f29315)