a-b-street/abstreet · warning · anyhow::Error
multipolygon was unexpectedly empty
Error message
multipolygon was unexpectedly empty
What it means
While iterating census area features from the flatgeobuf file, each MultiPolygon geometry is converted and only its first polygon is kept. If the MultiPolygon has zero polygons (an empty geometry), .first() is None and this error is thrown. It signals a malformed/degenerate census feature rather than a caller mistake.
Solutions
- Skip empty MultiPolygon features with a warning instead of failing the whole import
- Regenerate/re-download population_areas.fgb from the source
- Validate geometry non-emptiness in the import handbook pipeline before publishing the fgb
Example fix
// before
let geo_polygon = multi_poly.0.first().ok_or_else(|| anyhow!("multipolygon was unexpectedly empty"))?;
// after
let geo_polygon = match multi_poly.0.first() {
Some(p) => p,
None => {
warn!("census area {:?} has an empty multipolygon; skipping", props);
continue;
}
}; Defensive patterns
Strategy: fallback
Validate before calling
// treat empty multipolygons as skippable at the decode site
if let geo::Geometry::MultiPolygon(mp) = &geom_result {
if mp.0.is_empty() { /* skip feature */ }
} Type guard
fn non_empty_multipolygon(g: &geo::Geometry<f64>) -> Option<&geo::MultiPolygon<f64>> {
match g { geo::Geometry::MultiPolygon(mp) if !mp.0.is_empty() => Some(mp), _ => None }
} Try / catch
match fetch_all_for_map(map).await { Err(e) if e.to_string().contains("unexpectedly empty") => warn!("some census features were empty; results partial"), r => r } Prevention
- Re-download the fgb if empty features appear
- Clip queries with a slightly padded bbox to avoid zero-area clips
- Log offending feature properties for upstream fixes
- Prefer skipping over failing the whole population import
When it happens
Trigger: fetch_all_for_map reads a feature whose decoded geometry yields geo::Geometry::MultiPolygon with an empty Vec — an empty census area polygon in population_areas.fgb, or a failed/partial geometry decode by GeoWriter.
Common situations: Upstream changes to the census source producing empty polygons for some regions; querying the FGB at a bbox edge that clips features to nothing; stale or corrupted cached fgb data.
Related errors
- missing bound rect
- Traffic signal assignment for
- {}
- Unexpected geometry type for
- Some trip has negative departure time
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/ff31fb08ff9473ce.
Report an issue: GitHub.
Appendix: source
Thrown at popdat/src/import_census.rs:60
if !props.contains_key("population") {
warn!("skipping feature with missing population");
continue;
}
let population: usize = props["population"].parse()?;
let geometry = match feature.geometry() {
Some(g) => g,
None => {
warn!("skipping feature with missing geometry");
continue;
}
};
let mut geo = GeoWriter::new();
geometry.process(&mut geo, flatgeobuf::GeometryType::MultiPolygon)?;
if let Some(geo::Geometry::MultiPolygon(multi_poly)) = geo.take_geometry() {
let geo_polygon = multi_poly
.0
.first()
.ok_or_else(|| anyhow!("multipolygon was unexpectedly empty"))?;
if multi_poly.0.len() > 1 {
warn!(
"dropping {} extra polygons from census area: {:?}",
multi_poly.0.len() - 1,
props
);
}
if !geo_polygon.intersects(&geo_map_area) {
debug!(
"skipping polygon outside of map area. polygon: {:?}, map_area: {:?}",
geo_polygon, geo_map_area
);
continue;
}
let mut polygon = geo_polygon.clone();
polygon.map_coords_in_place(|c| geom::LonLat::new(c.x, c.y).to_pt(bounds).into());View on GitHub (pinned to 0964f29315)