Various fixes

This commit is contained in:
Andras Schmelczer 2026-02-04 22:29:42 +00:00
parent 34a4d0ba86
commit 55598aaaa0
14 changed files with 1250 additions and 130 deletions

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use axum::extract::Query;
use axum::extract::{Path, Query};
use axum::http::StatusCode;
use axum::response::Json;
use rustc_hash::FxHashMap;
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use tracing::info;
use crate::parsing::{parse_bounds, parse_filters, row_passes_filters};
use crate::parsing::{bounds_intersect, parse_bounds, parse_filters, row_passes_filters};
use crate::state::AppState;
#[derive(Serialize)]
@ -96,7 +96,7 @@ pub async fn get_postcodes(
let filters_str = params.filters.clone();
let (parsed_filters, parsed_enum_filters) = parse_filters(
params.filters.as_deref(),
&state.data.feature_names,
&state.feature_name_to_index,
&state.data.enum_values,
);
let num_filters = parsed_filters.len() + parsed_enum_filters.len();
@ -113,11 +113,7 @@ pub async fn get_postcodes(
if name.is_empty() {
return None;
}
state
.data
.feature_names
.iter()
.position(|feat| feat == name)
state.feature_name_to_index.get(name).copied()
})
.collect()
});
@ -134,12 +130,6 @@ pub async fn get_postcodes(
let has_selective = field_indices.is_some();
let sel_indices = field_indices.as_deref().unwrap_or(&[]);
// Step 1: Find postcodes within bounds using spatial grid on centroids
let postcode_indices: Vec<u32> = postcode_data.grid.query(south, west, north, east);
// Step 2: For each postcode, aggregate properties
let mut postcode_aggs: FxHashMap<usize, PostcodeAgg> = FxHashMap::default();
// Build postcode -> rows mapping by iterating properties in bounds
// and grouping by their postcode
let mut postcode_rows: FxHashMap<usize, Vec<usize>> = FxHashMap::default();
@ -165,24 +155,23 @@ pub async fn get_postcodes(
}
});
// Now aggregate for each postcode that's in bounds and has properties
for &pc_idx in &postcode_indices {
let idx = pc_idx as usize;
if let Some(rows) = postcode_rows.get(&idx) {
let agg = postcode_aggs
.entry(idx)
.or_insert_with(|| PostcodeAgg::new(num_features));
for &row in rows {
if has_selective {
agg.add_row_selective(feature_data, row, num_features, sel_indices);
} else {
agg.add_row(feature_data, row, num_features);
}
// Aggregate for each postcode that has properties in bounds
// (polygon intersection check happens later when building response)
let mut postcode_aggs: FxHashMap<usize, PostcodeAgg> = FxHashMap::default();
for (&pc_idx, rows) in &postcode_rows {
let agg = postcode_aggs
.entry(pc_idx)
.or_insert_with(|| PostcodeAgg::new(num_features));
for &row in rows {
if has_selective {
agg.add_row_selective(feature_data, row, num_features, sel_indices);
} else {
agg.add_row(feature_data, row, num_features);
}
}
}
// Build response
// Build response, filtering postcodes to only those whose polygon intersects query bounds
let mut features = Vec::with_capacity(postcode_aggs.len());
for (pc_idx, aggregation) in postcode_aggs {
@ -190,6 +179,23 @@ pub async fn get_postcodes(
continue;
}
// Compute postcode polygon bounding box and check intersection with query bounds
let vertices = &postcode_data.vertices[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 &[lon, lat] in vertices {
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; }
}
if !bounds_intersect(pc_south, pc_west, pc_north, pc_east, south, west, north, east) {
continue;
}
let mut map = Map::new();
map.insert(
"postcode".into(),
@ -198,7 +204,7 @@ pub async fn get_postcodes(
map.insert("count".into(), Value::Number(aggregation.count.into()));
// Add vertices as array of [lon, lat] pairs
let vertices_array: Vec<Value> = postcode_data.vertices[pc_idx]
let vertices_array: Vec<Value> = vertices
.iter()
.map(|[lon, lat]| Value::Array(vec![Value::from(*lon as f64), Value::from(*lat as f64)]))
.collect();
@ -244,3 +250,44 @@ pub async fn get_postcodes(
Ok(Json(response))
}
#[derive(Serialize)]
pub struct PostcodeLookupResponse {
pub postcode: String,
pub latitude: f64,
pub longitude: f64,
/// Polygon vertices as [[lon, lat], ...] for rendering highlight
pub vertices: Vec<[f64; 2]>,
}
/// Look up a single postcode and return its centroid coordinates and polygon.
pub async fn get_postcode_lookup(
state: Arc<AppState>,
Path(postcode): Path<String>,
) -> Result<Json<PostcodeLookupResponse>, StatusCode> {
// Normalize the postcode: uppercase, remove extra spaces, ensure single space
let normalized = postcode
.to_uppercase()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
let postcode_data = &state.postcode_data;
if let Some(&idx) = postcode_data.postcode_to_idx.get(&normalized) {
let (lat, lon) = postcode_data.centroids[idx];
let vertices: Vec<[f64; 2]> = postcode_data.vertices[idx]
.iter()
.map(|[lo, la]| [*lo as f64, *la as f64])
.collect();
info!(postcode = %normalized, "GET /api/postcode/{postcode}");
Ok(Json(PostcodeLookupResponse {
postcode: normalized,
latitude: lat as f64,
longitude: lon as f64,
vertices,
}))
} else {
Err(StatusCode::NOT_FOUND)
}
}