good stuff
This commit is contained in:
parent
ea8389ef40
commit
f4de0eeb9f
39 changed files with 5165 additions and 348 deletions
|
|
@ -7,6 +7,7 @@ use axum::Extension;
|
|||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use metrics::histogram;
|
||||
use tracing::info;
|
||||
|
||||
use crate::aggregation::Aggregator;
|
||||
|
|
@ -38,34 +39,6 @@ pub struct PostcodeParams {
|
|||
travel: Option<String>,
|
||||
}
|
||||
|
||||
/// Build a GeoJSON geometry object from postcode polygon rings.
|
||||
/// Returns Polygon for 1 ring, MultiPolygon for 2+ rings.
|
||||
fn build_postcode_geometry(rings: &[Vec<[f32; 2]>]) -> Value {
|
||||
if rings.len() == 1 {
|
||||
let coords: Vec<Value> = rings[0]
|
||||
.iter()
|
||||
.map(|[lon, lat]| {
|
||||
Value::Array(vec![Value::from(*lon as f64), Value::from(*lat as f64)])
|
||||
})
|
||||
.collect();
|
||||
serde_json::json!({ "type": "Polygon", "coordinates": [coords] })
|
||||
} else {
|
||||
let polys: Vec<Value> = rings
|
||||
.iter()
|
||||
.map(|ring| {
|
||||
let coords: Vec<Value> = ring
|
||||
.iter()
|
||||
.map(|[lon, lat]| {
|
||||
Value::Array(vec![Value::from(*lon as f64), Value::from(*lat as f64)])
|
||||
})
|
||||
.collect();
|
||||
Value::Array(vec![Value::Array(coords)])
|
||||
})
|
||||
.collect();
|
||||
serde_json::json!({ "type": "MultiPolygon", "coordinates": polys })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_postcodes(
|
||||
state: Arc<AppState>,
|
||||
Extension(user): Extension<OptionalUser>,
|
||||
|
|
@ -128,9 +101,8 @@ pub async fn get_postcodes(
|
|||
let has_selective = field_indices.is_some();
|
||||
let sel_indices = field_indices.as_deref().unwrap_or(&[]);
|
||||
|
||||
// Build postcode -> rows mapping by iterating properties in bounds
|
||||
// and grouping by their postcode
|
||||
let mut postcode_rows: FxHashMap<usize, Vec<usize>> = FxHashMap::default();
|
||||
// Single-pass: aggregate directly into postcode_aggs while iterating properties in bounds
|
||||
let mut postcode_aggs: FxHashMap<usize, Aggregator> = FxHashMap::default();
|
||||
|
||||
state
|
||||
.grid
|
||||
|
|
@ -146,16 +118,22 @@ pub async fn get_postcodes(
|
|||
return;
|
||||
}
|
||||
|
||||
// Get postcode for this property
|
||||
let postcode = state.data.postcode(row);
|
||||
if let Some(&pc_idx) = postcode_data.postcode_to_idx.get(postcode) {
|
||||
postcode_rows.entry(pc_idx).or_default().push(row);
|
||||
let agg = postcode_aggs
|
||||
.entry(pc_idx)
|
||||
.or_insert_with(|| Aggregator::new(num_features));
|
||||
if has_selective {
|
||||
agg.add_row_selective(feature_data, row, num_features, sel_indices, &quant);
|
||||
} else {
|
||||
agg.add_row(feature_data, row, num_features, &quant);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Filter postcodes by travel time range (if specified)
|
||||
if has_travel {
|
||||
postcode_rows.retain(|&pc_idx, _rows| {
|
||||
postcode_aggs.retain(|&pc_idx, _agg| {
|
||||
let postcode = &postcode_data.postcodes[pc_idx];
|
||||
for (ti, entry) in travel_entries.iter().enumerate() {
|
||||
if let (Some(fmin), Some(fmax)) = (entry.filter_min, entry.filter_max) {
|
||||
|
|
@ -176,26 +154,10 @@ pub async fn get_postcodes(
|
|||
});
|
||||
}
|
||||
|
||||
// Aggregate for each postcode that has properties in bounds
|
||||
// (polygon intersection check happens later when building response)
|
||||
let mut postcode_aggs: FxHashMap<usize, Aggregator> = FxHashMap::default();
|
||||
// Travel time aggregation per postcode
|
||||
let mut travel_aggs: FxHashMap<usize, Vec<TravelTimeAgg>> = FxHashMap::default();
|
||||
|
||||
for (&pc_idx, rows) in &postcode_rows {
|
||||
let agg = postcode_aggs
|
||||
.entry(pc_idx)
|
||||
.or_insert_with(|| Aggregator::new(num_features));
|
||||
for &row in rows {
|
||||
if has_selective {
|
||||
agg.add_row_selective(feature_data, row, num_features, sel_indices, &quant);
|
||||
} else {
|
||||
agg.add_row(feature_data, row, num_features, &quant);
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate travel times for this postcode
|
||||
if has_travel {
|
||||
if has_travel {
|
||||
for &pc_idx in postcode_aggs.keys() {
|
||||
let postcode = &postcode_data.postcodes[pc_idx];
|
||||
let tt_aggs = travel_aggs.entry(pc_idx).or_insert_with(|| {
|
||||
(0..travel_entries.len())
|
||||
|
|
@ -225,37 +187,24 @@ pub async fn get_postcodes(
|
|||
continue;
|
||||
}
|
||||
|
||||
// Compute postcode polygon bounding box across ALL parts and check intersection
|
||||
let rings = &postcode_data.polygons[pc_idx];
|
||||
let (mut pc_south, mut pc_north) = (f64::INFINITY, f64::NEG_INFINITY);
|
||||
let (mut pc_west, mut pc_east) = (f64::INFINITY, f64::NEG_INFINITY);
|
||||
for ring in rings {
|
||||
for &[lon, lat] in ring {
|
||||
let lon_f = lon as f64;
|
||||
let lat_f = lat as f64;
|
||||
if lat_f < pc_south {
|
||||
pc_south = lat_f;
|
||||
}
|
||||
if lat_f > pc_north {
|
||||
pc_north = lat_f;
|
||||
}
|
||||
if lon_f < pc_west {
|
||||
pc_west = lon_f;
|
||||
}
|
||||
if lon_f > pc_east {
|
||||
pc_east = lon_f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use precomputed AABB for bounds intersection check
|
||||
let (pc_south, pc_west, pc_north, pc_east) = postcode_data.aabbs[pc_idx];
|
||||
|
||||
if !bounds_intersect(
|
||||
pc_south, pc_west, pc_north, pc_east, south, west, north, east,
|
||||
pc_south as f64,
|
||||
pc_west as f64,
|
||||
pc_north as f64,
|
||||
pc_east as f64,
|
||||
south,
|
||||
west,
|
||||
north,
|
||||
east,
|
||||
) {
|
||||
filtered_out += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let geometry = build_postcode_geometry(rings);
|
||||
let geometry = postcode_data.geometries[pc_idx].clone();
|
||||
|
||||
// Build properties
|
||||
let centroid = postcode_data.centroids[pc_idx];
|
||||
|
|
@ -327,6 +276,8 @@ pub async fn get_postcodes(
|
|||
}
|
||||
}
|
||||
|
||||
histogram!("postcodes_response_count").record(features.len() as f64);
|
||||
|
||||
let truncated = features.len() > MAX_CELLS_PER_REQUEST;
|
||||
let t_total = t0.elapsed();
|
||||
info!(
|
||||
|
|
@ -365,8 +316,7 @@ pub async fn get_postcode_lookup(
|
|||
|
||||
if let Some(&idx) = postcode_data.postcode_to_idx.get(&normalized) {
|
||||
let (lat, lon) = postcode_data.centroids[idx];
|
||||
let rings = &postcode_data.polygons[idx];
|
||||
let geometry = build_postcode_geometry(rings);
|
||||
let geometry = postcode_data.geometries[idx].clone();
|
||||
|
||||
info!(postcode = %normalized, "GET /api/postcode/{postcode}");
|
||||
Ok(Json(serde_json::json!({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue