{"record":{"id":"915108995184c062","repo":"a-b-street/abstreet","slug":"expected-exactly-1-feature","errorCode":null,"errorMessage":"Expected exactly 1 feature","messagePattern":"Expected exactly 1 feature","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"importer/src/pick_geofabrik.rs","lineNumber":45,"sourceCode":"    // Contains some directory structure, like north-america/us/wyoming-latest.osm.pbf or\n    // asia/yemen-latest.osm.pbf\n    let basename = url\n        .strip_prefix(\"https://download.geofabrik.de/\")\n        .expect(\"Geofabrik URLs changed\");\n    let local = abstio::path_shared_input(format!(\"geofabrik/{basename}\"));\n\n    Ok((url, local))\n}\n\nfn load_boundary(path: String) -> Result<geo::Polygon> {\n    let gj: GeoJson = abstio::maybe_read_json(path, &mut Timer::throwaway())?;\n    let mut features = match gj {\n        GeoJson::Feature(feature) => vec![feature],\n        GeoJson::FeatureCollection(feature_collection) => feature_collection.features,\n        _ => bail!(\"Unexpected geojson: {:?}\", gj),\n    };\n    if features.len() != 1 {\n        bail!(\"Expected exactly 1 feature\");\n    }\n    let poly: geo::Polygon = features\n        .pop()\n        .unwrap()\n        .geometry\n        .take()\n        .unwrap()\n        .value\n        .try_into()\n        .unwrap();\n    Ok(poly)\n}\n\nasync fn load_remote_geojson(path: String, url: &str) -> Result<GeoJson> {\n    if !abstio::file_exists(&path) {\n        info!(\"Downloading {}\", url);\n        abstio::download_to_file(url, None, &path).await?;\n    }","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/importer/src/pick_geofabrik.rs#L27-L63","documentation":"After collecting features from the GeoJSON (Feature or FeatureCollection), load_boundary requires exactly one feature because the boundary must be a single polygon. If the file contains zero features (empty collection) or more than one, it bails with 'Expected exactly 1 feature'.","triggerScenarios":"pick_geofabrik given a boundary.geojson whose FeatureCollection has an empty features array, or multiple features (e.g. multi-part boundary exported as separate features).","commonSituations":"Downloading a clipped OSM boundary that was exported as multiple polygons; a nearly-empty GeoJSON file created by an export filter that matched nothing.","solutions":["Inspect the file's features array; merge multiple polygons into a single MultiPolygon feature, or delete extras so exactly one remains.","If empty, re-download/regenerate the boundary for the correct region.","Wrap multiple polygons into one MultiPolygon geometry so the file still has one feature."],"exampleFix":"// before\n{\"type\": \"FeatureCollection\", \"features\": [f1, f2]}\n// after\n{\"type\": \"FeatureCollection\", \"features\": [{\"type\": \"Feature\", \"geometry\": {\"type\": \"MultiPolygon\", \"coordinates\": [f1.geom, f2.geom]}}]}","handlingStrategy":"validation","validationCode":"let features: Vec<_> = match &gj {\n    GeoJson::Feature(f) => vec![f],\n    GeoJson::FeatureCollection(fc) => &fc.features,\n    _ => bail!(\"not a feature/collection\"),\n};\nif features.len() != 1 { bail!(\"boundary file must contain exactly one feature, got {}\", features.len()); }","typeGuard":"fn single_feature(gj: &GeoJson) -> Option<&geojson::Feature> {\n    match gj {\n        GeoJson::Feature(f) => Some(f),\n        GeoJson::FeatureCollection(fc) if fc.features.len() == 1 => fc.features.first(),\n        _ => None,\n    }\n}","tryCatchPattern":"let poly = match load_boundary(path) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"exactly 1 feature\") => {\n        eprintln!(\"Merge the boundary into a single feature: {e}\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Dissolve multi-part boundaries into one MultiPolygon feature before importing","Never export empty selections from the GIS tool — verify feature count after export","Check feature count with jq '.features | length' before running the importer"],"tags":["geojson","input-validation","importer"],"backgroundTag":"empty-result-set","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"}