a-b-street/abstreet · error

Unexpected geometry type for

Error message

Unexpected geometry type for {:?}

What it means

clip_zones parses GeoJSON census features into geo::Geometry and only accepts Polygon or MultiPolygon (taking the first polygon of a MultiPolygon). Any other geometry type — e.g. LineString, Point, or GeometryCollection — hits the catch-all bail. It protects downstream polygon clipping, which requires areal geometry.

Solutions

  1. Filter the FeatureCollection to features whose geometry.type is "Polygon" or "MultiPolygon" before calling clip_zones.
  2. Inspect the failing feature's properties (printed in the message) and remove/fix that record in the source data.
  3. Buffer points/lines into polygons beforehand if they are legitimate zones.
  4. Patch clip_zones to handle the geometry type (e.g. keep all polygons of a MultiPolygon, skip others).

Example fix

// before: feed all features
let zones = clip_zones(&boundary, gj_str)?;
// after: pre-filter
let kept: Vec<_> = fc.features.into_iter().filter(|f| matches!(f.geometry_type(), "Polygon" | "MultiPolygon")).collect();
Defensive patterns

Strategy: validation

Validate before calling

let ok = fc.features.iter().all(|f| {
    matches!(f.geometry_type(), "Polygon" | "MultiPolygon")
});
if !ok { return Err("census data contains non-polygon features".into()); }

Type guard

fn is_areal(f: &GeoJsonFeature) -> bool {
    matches!(f.geometry_type(), "Polygon" | "MultiPolygon")
}

Try / catch

match clip_zones(&boundary, gj_str) {
    Err(e) if e.to_string().starts_with("Unexpected geometry type") => {
        // filter out the offending feature (named in the message) and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling clip_zones (popgetter/src/lib.rs:46) with an input GeoJSON FeatureCollection whose features contain non-(multi)polygon geometries; the `try_into` succeeds (any geometry is allowed) but the match falls through to `_ => bail!`.

Common situations: Census boundary downloads containing point/line features (address points, boundary segments), mixed collections, or TopoJSON conversions producing unusual shapes; TODO in code notes MultiPolygon handling is itself questionable.

Related errors


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

Appendix: source

Thrown at popgetter/src/lib.rs:46

}

/// Clips existing TopoJSON files to the given boundary. All polygons are in WGS84.
pub fn clip_zones(
    topojson_path: &str,
    boundary: geo::Polygon<f64>,
) -> Result<Vec<(geo::Polygon<f64>, CensusZone)>> {
    let gj = load_all_zones_as_geojson(topojson_path)?;

    let start = Instant::now();
    let mut output = Vec::new();
    for gj_feature in gj {
        let geom: geo::Geometry<f64> = gj_feature.clone().try_into()?;
        if boundary.intersects(&geom) {
            let polygon = match geom {
                geo::Geometry::Polygon(p) => p,
                // TODO What're these, and what should we do with them?
                geo::Geometry::MultiPolygon(mut mp) => mp.0.remove(0),
                _ => bail!("Unexpected geometry type for {:?}", gj_feature.properties),
            };
            let census_zone = CensusZone {
                id: gj_feature
                    .property("ID")
                    .unwrap()
                    .as_str()
                    .unwrap()
                    .to_string(),
                cars_0: gj_feature
                    .property("cars_0")
                    .unwrap()
                    .as_u64()
                    .unwrap()
                    .try_into()?,
                cars_1: gj_feature
                    .property("cars_1")
                    .unwrap()
                    .as_u64()

View on GitHub (pinned to 0964f29315)