a-b-street/abstreet · error

isn't a traffic signal

Error message

{} isn't a traffic signal

What it means

The /traffic-signals/get command looks up an intersection and returns its ControlTrafficSignal state. Only intersections actually controlled by a traffic signal exist in the signals map; if the given IntersectionID is an stop sign, border, or uncontrolled intersection, the library bails with this error.

Solutions

  1. Verify the intersection is a traffic signal (i.is_traffic_signal()) before querying; enumerate map.all_lts / signal IDs instead of guessing.
  2. Get the ID from the map itself (e.g. by filtering intersections whose control is a signal) rather than hardcoding.
  3. Refresh your ID references after re-importing the map, since control types can change between imports.
Defensive patterns

Strategy: validation

Validate before calling

// enumerate only signalized intersections from the map first
const signalIds = map.intersections.filter(i => i.is_traffic_signal()).map(i => i.id);

Type guard

function isTrafficSignal(i) { return i.control === "TrafficSignal"; }

Try / catch

try {
  ts = await post("/traffic-signals/get", { id });
} catch (e) {
  if (String(e).includes("isn't a traffic signal")) ts = null;
}

Prevention

When it happens

Trigger: POST /traffic-signals/get with id=<usize> where maybe_get_traffic_signal(IntersectionID(id)) returns None — the intersection exists but isn't signalized.

Common situations: Iterating all intersection IDs instead of only traffic-signal IDs; map re-imports that changed an intersection's control type; hardcoded IDs from a different map.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at headless/src/main.rs:220

                }
            }

            let mut scenario = Scenario::empty(map, "one-shot");
            scenario.people = ExternalPerson::import(map, vec![input], false)?;
            let mut rng = XorShiftRng::seed_from_u64(load.rng_seed);
            sim.instantiate(&scenario, map, &mut rng, &mut Timer::throwaway());
            Ok(format!(
                "{} created",
                sim.get_all_people().last().unwrap().id
            ))
        }
        // Traffic signals
        "/traffic-signals/get" => {
            let i = IntersectionID(get("id")?.parse::<usize>()?);
            if let Some(ts) = map.maybe_get_traffic_signal(i) {
                Ok(abstutil::to_json(ts))
            } else {
                bail!("{} isn't a traffic signal", i)
            }
        }
        "/traffic-signals/set" => {
            let ts: ControlTrafficSignal = abstutil::from_json(body)?;
            let id = ts.id;

            // incremental_edit_traffic_signal is the cheap option, but since we may need to call
            // get-edits later, go through the proper flow.
            let mut edits = map.get_edits().clone();
            edits.commands.push(map.edit_intersection_cmd(id, |new| {
                new.control = EditIntersectionControl::TrafficSignal(ts.export(map));
            }));
            map.must_apply_edits(edits, &mut Timer::throwaway());
            map.recalculate_pathfinding_after_edits(&mut Timer::throwaway());

            Ok(format!("{} has been updated", id))
        }
        "/traffic-signals/get-delays" => {

View on GitHub (pinned to 0964f29315)