good stuff

This commit is contained in:
Andras Schmelczer 2026-03-15 21:10:54 +00:00
parent ea8389ef40
commit f4de0eeb9f
39 changed files with 5165 additions and 348 deletions

View file

@ -38,11 +38,12 @@ struct Properties {
pub struct PostcodeData {
/// Postcode strings
pub postcodes: Vec<String>,
/// All polygon parts per postcode: polygons[i] = list of outer rings
/// Single Polygon → 1 ring, MultiPolygon → N rings
pub polygons: Vec<Vec<Vec<[f32; 2]>>>,
/// Centroid (lat, lon) for lookups
pub centroids: Vec<(f32, f32)>,
/// Precomputed AABB per postcode: (south, west, north, east) as f32
pub aabbs: Vec<(f32, f32, f32, f32)>,
/// Precomputed GeoJSON geometry Value per postcode
pub geometries: Vec<serde_json::Value>,
/// Lookup from postcode string to index
pub postcode_to_idx: FxHashMap<String, usize>,
}
@ -96,6 +97,7 @@ impl PostcodeData {
let mut local_postcodes = Vec::new();
let mut local_polygons = Vec::new();
let mut local_centroids = Vec::new();
let mut local_aabbs: Vec<(f32, f32, f32, f32)> = Vec::new();
for feature in collection.features {
let postcode = feature.properties.postcodes;
@ -140,20 +142,44 @@ impl PostcodeData {
(sum_lat / count, sum_lon / count)
};
// Compute AABB across all rings
let (mut aabb_south, mut aabb_north) = (f32::INFINITY, f32::NEG_INFINITY);
let (mut aabb_west, mut aabb_east) = (f32::INFINITY, f32::NEG_INFINITY);
for ring in &rings {
for &[lon, lat] in ring {
if lat < aabb_south {
aabb_south = lat;
}
if lat > aabb_north {
aabb_north = lat;
}
if lon < aabb_west {
aabb_west = lon;
}
if lon > aabb_east {
aabb_east = lon;
}
}
}
local_postcodes.push(postcode);
local_polygons.push(rings);
local_centroids.push(centroid);
local_aabbs.push((aabb_south, aabb_west, aabb_north, aabb_east));
}
Ok::<_, anyhow::Error>((local_postcodes, local_polygons, local_centroids))
Ok::<_, anyhow::Error>((local_postcodes, local_polygons, local_centroids, local_aabbs))
})
.collect::<Result<Vec<_>, _>>()?;
let mut aabbs: Vec<(f32, f32, f32, f32)> = Vec::new();
// Flatten results
for (local_postcodes, local_polygons, local_centroids) in file_results {
for (local_postcodes, local_polygons, local_centroids, local_aabbs) in file_results {
postcodes.extend(local_postcodes);
polygons.extend(local_polygons);
centroids.extend(local_centroids);
aabbs.extend(local_aabbs);
}
debug!(
@ -167,12 +193,49 @@ impl PostcodeData {
postcode_to_idx.insert(postcode.clone(), idx);
}
// Precompute GeoJSON geometry for each postcode
let geometries: Vec<serde_json::Value> = polygons
.iter()
.map(|rings| {
if rings.len() == 1 {
let coords: Vec<serde_json::Value> = rings[0]
.iter()
.map(|[lon, lat]| {
serde_json::Value::Array(vec![
serde_json::Value::from(*lon as f64),
serde_json::Value::from(*lat as f64),
])
})
.collect();
serde_json::json!({"type": "Polygon", "coordinates": [coords]})
} else {
let polys: Vec<serde_json::Value> = rings
.iter()
.map(|ring| {
let coords: Vec<serde_json::Value> = ring
.iter()
.map(|[lon, lat]| {
serde_json::Value::Array(vec![
serde_json::Value::from(*lon as f64),
serde_json::Value::from(*lat as f64),
])
})
.collect();
serde_json::Value::Array(vec![serde_json::Value::Array(coords)])
})
.collect();
serde_json::json!({"type": "MultiPolygon", "coordinates": polys})
}
})
.collect();
info!(postcodes = postcodes.len(), "Postcode boundary data ready");
Ok(PostcodeData {
postcodes,
polygons,
centroids,
aabbs,
geometries,
postcode_to_idx,
})
}