a-b-street/abstreet · error

No common roads

Error message

No common roads

What it means

Perimeter::try_to_merge merges two adjacent block perimeters, which requires them to share at least one road. The two perimeters' road sets are disjoint, so no merge is possible and the function bails (optionally warning first when debug_failures is set).

Solutions

  1. Pre-check that the two perimeters share a road before calling try_to_merge and skip non-adjacent pairs.
  2. Verify the blocks really are neighbors; only merge blocks produced from the same map and same skip configuration.
  3. If debug_failures was enabled, read the warning output to identify which block pair is disjoint and exclude it upstream.

Example fix

// before
merged = a.try_to_merge(map, b, &mut small_roads, debug_failures)?;
// after
let a_roads: HashSet<RoadID> = a.roads.iter().map(|id| id.road).collect();
let b_roads: HashSet<RoadID> = b.roads.iter().map(|id| id.road).collect();
if a_roads.is_disjoint(&b_roads) { continue; } // not adjacent, skip
merged = a.try_to_merge(map, b, &mut small_roads, debug_failures)?;
Defensive patterns

Strategy: validation

Validate before calling

let a_set: HashSet<RoadID> = a.roads.iter().map(|rs| rs.road).collect();
let b_set: HashSet<RoadID> = b.roads.iter().map(|rs| rs.road).collect();
anyhow::ensure!(!a_set.is_disjoint(&b_set), "perimeters not adjacent");

Type guard

fn perimeters_adjacent(a: &Perimeter, b: &Perimeter) -> bool {
    let a_set: HashSet<RoadID> = a.roads.iter().map(|rs| rs.road).collect();
    b.roads.iter().any(|rs| a_set.contains(&rs.road))
}

Prevention

When it happens

Trigger: Calling try_to_merge(other) on a Perimeter whose roads share no RoadID with `other`'s roads — the intersection of the two RoadID sets is empty.

Common situations: Block merging passes where blocks that merely touch at a point (or are separated by an untraced/skipped road) are fed into the merger; running merging after changing the skip set so previously adjacent blocks no longer overlap.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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

Appendix: source

Thrown at blockfinding/src/lib.rs:209

    fn try_to_merge(
        &mut self,
        map: &Map,
        other: &mut Perimeter,
        debug_failures: bool,
    ) -> Result<()> {
        for reverse_to_fix_winding_order in [false, true] {
            self.undo_invariant();
            other.undo_invariant();

            // Calculate common roads
            let roads1: HashSet<RoadID> = self.roads.iter().map(|id| id.road).collect();
            let roads2: HashSet<RoadID> = other.roads.iter().map(|id| id.road).collect();
            let common: HashSet<RoadID> = roads1.intersection(&roads2).cloned().collect();
            if common.is_empty() {
                if debug_failures {
                    warn!("No common roads");
                }
                bail!("No common roads");
            }

            // "Rotate" the order of roads, so that all of the overlapping roads are at the end of the
            // list. If the entire perimeter is surrounded by the other, then no rotation needed.
            if self.roads.len() != common.len() {
                let mut i = 0;
                while common.contains(&self.roads[0].road)
                    || !common.contains(&self.roads.last().unwrap().road)
                {
                    self.roads.rotate_left(1);

                    i += 1;
                    if i == self.roads.len() {
                        bail!(
                            "Rotating {:?} against common {:?} infinite-looped",
                            self.roads,
                            common
                        );

View on GitHub (pinned to 0964f29315)