a-b-street/abstreet · error
Unexpected geojson
Error message
Unexpected geojson: {:?} What it means
load_boundary in pick_geofabrik reads a boundary file as GeoJSON and accepts only a single Feature or a FeatureCollection. Any other GeoJson variant (e.g. a bare Geometry like Polygon or GeometryCollection at the top level) hits the catch-all bail with this message, which debug-prints the parsed value.
Solutions
- Open the boundary file and ensure the top-level object is {"type": "Feature"...} or {"type": "FeatureCollection", "features": [...]}; wrap a bare geometry in a Feature.
- Re-export from the GIS tool as GeoJSON with features (not geometry-only).
- If you control the code, add a GeoJson::Geometry arm that wraps the geometry into a Feature before matching.
Example fix
// before
{"type": "Polygon", "coordinates": [[...]]}
// after
{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {}, "geometry": {"type": "Polygon", "coordinates": [[...]]}}]} Defensive patterns
Strategy: validation
Validate before calling
let gj: GeoJson = serde_json::from_slice(&bytes)?;
match gj {
GeoJson::Feature(_) | GeoJson::FeatureCollection(_) => {},
_ => return Err(anyhow!("boundary must be a Feature or FeatureCollection")),
} Type guard
fn is_feature_or_collection(gj: &GeoJson) -> bool {
matches!(gj, GeoJson::Feature(_) | GeoJson::FeatureCollection(_))
} Try / catch
match load_boundary(path) {
Ok(poly) => poly,
Err(e) if e.to_string().contains("Unexpected geojson") => {
eprintln!("Re-export the boundary as a Feature/FeatureCollection: {e}");
fallback_poly
}
Err(e) => return Err(e),
} Prevention
- Always export boundaries as GeoJSON FeatureCollections, not bare geometries
- Validate boundary files with geojson.io or a JSON schema check before importing
- Keep a canonical preprocessed boundary file per region instead of ad-hoc exports
When it happens
Trigger: Calling pick_geofabrik with a boundary path whose file contains top-level GeoJson that is neither a Feature nor a FeatureCollection — e.g. a raw geometry object like {"type": "Polygon", ...} produced by hand-rolled or exported GeoJSON.
Common situations: Hand-editing or exporting boundary GeoJSON from tools (QGIS geometry-only export, geojson.io geometry copy) that emits a bare geometry instead of a Feature/FeatureCollection; passing a wrong file that still parses as valid GeoJSON.
Related errors
- Expected exactly 1 feature
- study area geojson has
- Input is missing geo_code
- Started on a road we shouldn't trace
- doesn't have a column called Longitude, Latitude, or…
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/61d9dd99287cfef7.
Report an issue: GitHub.
Appendix: source
Thrown at importer/src/pick_geofabrik.rs:42
.min_by_key(|(mp, _)| mp.unsigned_area() as usize)
.unwrap();
// Contains some directory structure, like north-america/us/wyoming-latest.osm.pbf or
// asia/yemen-latest.osm.pbf
let basename = url
.strip_prefix("https://download.geofabrik.de/")
.expect("Geofabrik URLs changed");
let local = abstio::path_shared_input(format!("geofabrik/{basename}"));
Ok((url, local))
}
fn load_boundary(path: String) -> Result<geo::Polygon> {
let gj: GeoJson = abstio::maybe_read_json(path, &mut Timer::throwaway())?;
let mut features = match gj {
GeoJson::Feature(feature) => vec![feature],
GeoJson::FeatureCollection(feature_collection) => feature_collection.features,
_ => bail!("Unexpected geojson: {:?}", gj),
};
if features.len() != 1 {
bail!("Expected exactly 1 feature");
}
let poly: geo::Polygon = features
.pop()
.unwrap()
.geometry
.take()
.unwrap()
.value
.try_into()
.unwrap();
Ok(poly)
}
async fn load_remote_geojson(path: String, url: &str) -> Result<GeoJson> {
if !abstio::file_exists(&path) {View on GitHub (pinned to 0964f29315)