1use anyhow::Result;
2use geo::{BoundingRect, Intersects, MapCoordsInPlace};
3
4use geom::{GPSBounds, Polygon};
5
6use crate::CensusArea;
7
8impl CensusArea {
9 pub async fn fetch_all_for_map(
10 map_area: &Polygon,
11 bounds: &GPSBounds,
12 ) -> Result<Vec<CensusArea>> {
13 use flatgeobuf::HttpFgbReader;
14 use geozero::geo_types::GeoWriter;
15
16 let mut geo_map_area: geo::Polygon = map_area.clone().into();
17 geo_map_area.map_coords_in_place(|c| {
18 let projected = geom::Pt2D::new(c.x, c.y).to_gps(bounds);
19 (projected.x(), projected.y()).into()
20 });
21
22 let bounding_rect = geo_map_area
23 .bounding_rect()
24 .ok_or_else(|| anyhow!("missing bound rect"))?;
25
26 let mut fgb = HttpFgbReader::open("https://abstreet.s3.amazonaws.com/population_areas.fgb")
28 .await?
29 .select_bbox(
30 bounding_rect.min().x,
31 bounding_rect.min().y,
32 bounding_rect.max().x,
33 bounding_rect.max().y,
34 )
35 .await?;
36
37 let mut results = vec![];
38 while let Some(feature) = fgb.next().await? {
39 use flatgeobuf::FeatureProperties;
40 let props = feature.properties()?;
42 if !props.contains_key("population") {
43 warn!("skipping feature with missing population");
44 continue;
45 }
46 let population: usize = props["population"].parse()?;
47 let geometry = match feature.geometry() {
48 Some(g) => g,
49 None => {
50 warn!("skipping feature with missing geometry");
51 continue;
52 }
53 };
54 let mut geo = GeoWriter::new();
55 geometry.process(&mut geo, flatgeobuf::GeometryType::MultiPolygon)?;
56 if let Some(geo::Geometry::MultiPolygon(multi_poly)) = geo.take_geometry() {
57 let geo_polygon = multi_poly
58 .0
59 .first()
60 .ok_or_else(|| anyhow!("multipolygon was unexpectedly empty"))?;
61 if multi_poly.0.len() > 1 {
62 warn!(
63 "dropping {} extra polygons from census area: {:?}",
64 multi_poly.0.len() - 1,
65 props
66 );
67 }
68
69 if !geo_polygon.intersects(&geo_map_area) {
70 debug!(
71 "skipping polygon outside of map area. polygon: {:?}, map_area: {:?}",
72 geo_polygon, geo_map_area
73 );
74 continue;
75 }
76
77 let mut polygon = geo_polygon.clone();
78 polygon.map_coords_in_place(|c| geom::LonLat::new(c.x, c.y).to_pt(bounds).into());
79 results.push(CensusArea {
80 polygon,
81 population,
82 });
83 } else {
84 warn!("skipping unexpected geometry");
85 continue;
86 }
87 }
88
89 Ok(results)
90 }
91}