1use std::time::Instant;
2
3use anyhow::{bail, Result};
4use geo::Intersects;
5use geojson::Feature;
6use serde::{Deserialize, Serialize};
7use topojson::{to_geojson, TopoJson};
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct CensusZone {
11 pub id: String,
13
14 pub cars_0: u16,
17 pub cars_1: u16,
18 pub cars_2: u16,
19 pub cars_3: u16,
21}
22
23impl CensusZone {
24 pub fn total_cars(&self) -> u16 {
26 self.cars_1 + 2 * self.cars_2 + 3 * self.cars_3
27 }
28}
29
30pub fn clip_zones(
32 topojson_path: &str,
33 boundary: geo::Polygon<f64>,
34) -> Result<Vec<(geo::Polygon<f64>, CensusZone)>> {
35 let gj = load_all_zones_as_geojson(topojson_path)?;
36
37 let start = Instant::now();
38 let mut output = Vec::new();
39 for gj_feature in gj {
40 let geom: geo::Geometry<f64> = gj_feature.clone().try_into()?;
41 if boundary.intersects(&geom) {
42 let polygon = match geom {
43 geo::Geometry::Polygon(p) => p,
44 geo::Geometry::MultiPolygon(mut mp) => mp.0.remove(0),
46 _ => bail!("Unexpected geometry type for {:?}", gj_feature.properties),
47 };
48 let census_zone = CensusZone {
49 id: gj_feature
50 .property("ID")
51 .unwrap()
52 .as_str()
53 .unwrap()
54 .to_string(),
55 cars_0: gj_feature
56 .property("cars_0")
57 .unwrap()
58 .as_u64()
59 .unwrap()
60 .try_into()?,
61 cars_1: gj_feature
62 .property("cars_1")
63 .unwrap()
64 .as_u64()
65 .unwrap()
66 .try_into()?,
67 cars_2: gj_feature
68 .property("cars_2")
69 .unwrap()
70 .as_u64()
71 .unwrap()
72 .try_into()?,
73 cars_3: gj_feature
74 .property("cars_3")
75 .unwrap()
76 .as_u64()
77 .unwrap()
78 .try_into()?,
79 };
80 output.push((polygon, census_zone));
81 }
82 }
83 println!(
84 "Filtering took {:?}. {} results",
85 start.elapsed(),
86 output.len()
87 );
88
89 Ok(output)
90}
91
92fn load_all_zones_as_geojson(path: &str) -> Result<Vec<Feature>> {
93 let mut start = Instant::now();
94 let topojson_str = fs_err::read_to_string(path)?;
95 println!("Reading file took {:?}", start.elapsed());
96
97 start = Instant::now();
98 let topo = topojson_str.parse::<TopoJson>()?;
99 println!("Parsing topojson took {:?}", start.elapsed());
100
101 start = Instant::now();
102 let fc = match topo {
103 TopoJson::Topology(t) => to_geojson(&t, "zones")?,
104 _ => bail!("Unexpected topojson contents"),
105 };
106 println!("Converting to geojson took {:?}", start.elapsed());
107
108 Ok(fc.features)
109}