a-b-street/abstreet · error

No valid stops

Error message

No valid stops

What it means

While creating a transit route, each GTFS stop id must have been successfully created earlier (gtfs_to_stop_id). If none of the route's stops resolved to a TransitStopID, the route has no valid stops and cannot be constructed, so create_route bails.

Solutions

  1. Fix the upstream stop-creation failures (see stop-level errors) so at least one stop resolves.
  2. Check that route stop gtfs_ids match the ids in the stops feed (no key mismatch).
  3. Skip the route (the importer does) or trim the GTFS feed to routes with importable stops.
Defensive patterns

Strategy: validation

Validate before calling

let valid: Vec<_> = route.stops.iter()
    .filter(|id| gtfs_to_stop_id.contains_key(*id)).count();
if valid == 0 { /* skip route entirely before create_route */ }

Try / catch

match create_route(...) {
    Err(e) if e.to_string() == "No valid stops" => {
        warn!("skipping route {:?}", route.gtfs_id); Ok(None)
    }
    other => other.map(Some),
}

Prevention

When it happens

Trigger: finalize_transit -> create_route where every stop id in route.stops failed create_stop (or was filtered out), leaving the filtered stops vec empty.

Common situations: Routes whose stops all lie outside the map boundary or near sidewalks that couldn't snap; partial GTFS imports where stop creation failed wholesale for a route; mismatched gtfs_id keys between stops and routes feeds.

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/4d25868be5f6fbb4. Report an issue: GitHub.

Appendix: source

Thrown at map_model/src/make/transit.rs:157

        }
        snapper
    }
}

fn create_route(
    route: &RawTransitRoute,
    map: &mut Map,
    gtfs_to_stop_id: &HashMap<String, TransitStopID>,
    snapper: &BorderSnapper,
) -> Result<()> {
    // TODO At least warn about stops that failed to snap
    let stops: Vec<TransitStopID> = route
        .stops
        .iter()
        .filter_map(|gtfs_id| gtfs_to_stop_id.get(gtfs_id).cloned())
        .collect();
    if stops.is_empty() {
        bail!("No valid stops");
    }
    let border_snap_threshold = Distance::meters(30.0);

    let start = if map.boundary_polygon.contains_pt(route.shape.first_pt()) {
        map.get_ts(stops[0]).driving_pos.lane()
    } else {
        // Find the first time the route shape hits the map boundary
        let entry_pt = *map
            .boundary_polygon
            .get_outer_ring()
            .all_intersections(&route.shape)
            .get(0)
            .ok_or_else(|| anyhow!("couldn't find where shape enters map"))?;
        // Snap that to a border
        let borders = if route.route_type == RawTransitType::Bus {
            &snapper.bus_incoming_borders
        } else {
            &snapper.train_incoming_borders

View on GitHub (pinned to 0964f29315)