Add pocketbase and other changes

This commit is contained in:
Andras Schmelczer 2026-02-07 19:20:22 +00:00
parent a9717d570d
commit 229150b641
14 changed files with 1178 additions and 91 deletions

View file

@ -13,6 +13,7 @@ use crate::state::AppState;
#[derive(Serialize)]
pub struct PostcodesResponse {
r#type: &'static str,
features: Vec<Map<String, Value>>,
}
@ -181,37 +182,88 @@ pub async fn get_postcodes(
continue;
}
// Compute postcode polygon bounding box and check intersection with query bounds
let vertices = &postcode_data.vertices[pc_idx];
// 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 &[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; }
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;
}
}
}
if !bounds_intersect(pc_south, pc_west, pc_north, pc_east, south, west, north, east) {
if !bounds_intersect(
pc_south, pc_west, pc_north, pc_east, south, west, north, east,
) {
filtered_out += 1;
continue;
}
let mut map = Map::new();
map.insert(
// Build GeoJSON geometry: Polygon (1 ring) or MultiPolygon (2+ rings)
let geometry = 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();
let mut geo = Map::new();
geo.insert("type".into(), Value::String("Polygon".into()));
geo.insert(
"coordinates".into(),
Value::Array(vec![Value::Array(coords)]),
);
geo
} 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();
let mut geo = Map::new();
geo.insert("type".into(), Value::String("MultiPolygon".into()));
geo.insert("coordinates".into(), Value::Array(polys));
geo
};
// Build properties
let centroid = postcode_data.centroids[pc_idx];
let mut props = Map::new();
props.insert(
"postcode".into(),
Value::String(postcode_data.postcodes[pc_idx].clone()),
);
map.insert("count".into(), Value::Number(aggregation.count.into()));
// Add vertices as array of [lon, lat] pairs
let vertices_array: Vec<Value> = vertices
.iter()
.map(|[lon, lat]| Value::Array(vec![Value::from(*lon as f64), Value::from(*lat as f64)]))
.collect();
map.insert("vertices".into(), Value::Array(vertices_array));
props.insert("count".into(), Value::Number(aggregation.count.into()));
props.insert(
"centroid".into(),
Value::Array(vec![
Value::from(centroid.1 as f64), // lon
Value::from(centroid.0 as f64), // lat
]),
);
let iter: Box<dyn Iterator<Item = usize>> = if let Some(idx) = field_indices.as_ref() {
Box::new(idx.iter().copied())
@ -227,13 +279,19 @@ pub async fn get_postcodes(
serde_json::Number::from_f64(aggregation.mins[feat_index] as f64),
serde_json::Number::from_f64(aggregation.maxs[feat_index] as f64),
) {
map.insert(min_keys[feat_index].clone(), Value::Number(min_num));
map.insert(max_keys[feat_index].clone(), Value::Number(max_num));
props.insert(min_keys[feat_index].clone(), Value::Number(min_num));
props.insert(max_keys[feat_index].clone(), Value::Number(max_num));
}
}
}
features.push(map);
// Build GeoJSON Feature
let mut feature = Map::new();
feature.insert("type".into(), Value::String("Feature".into()));
feature.insert("geometry".into(), Value::Object(geometry));
feature.insert("properties".into(), Value::Object(props));
features.push(feature);
}
let t_total = t0.elapsed();
@ -248,7 +306,10 @@ pub async fn get_postcodes(
"GET /api/postcodes"
);
Ok(PostcodesResponse { features })
Ok(PostcodesResponse {
r#type: "FeatureCollection",
features,
})
})
.await
.map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()))?
@ -257,20 +318,11 @@ 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.
/// Look up a single postcode and return its centroid coordinates and geometry.
pub async fn get_postcode_lookup(
state: Arc<AppState>,
Path(postcode): Path<String>,
) -> Result<Json<PostcodeLookupResponse>, StatusCode> {
) -> Result<Json<Value>, StatusCode> {
// Normalize the postcode: uppercase, remove extra spaces, ensure single space
let normalized = postcode
.to_uppercase()
@ -282,17 +334,40 @@ 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 vertices: Vec<[f64; 2]> = postcode_data.vertices[idx]
.iter()
.map(|[lo, la]| [*lo as f64, *la as f64])
.collect();
let rings = &postcode_data.polygons[idx];
// Build GeoJSON geometry
let geometry = if rings.len() == 1 {
let coords: Vec<Value> = rings[0]
.iter()
.map(|[lo, la]| {
Value::Array(vec![Value::from(*lo as f64), Value::from(*la 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(|[lo, la]| {
Value::Array(vec![Value::from(*lo as f64), Value::from(*la as f64)])
})
.collect();
Value::Array(vec![Value::Array(coords)])
})
.collect();
serde_json::json!({ "type": "MultiPolygon", "coordinates": polys })
};
info!(postcode = %normalized, "GET /api/postcode/{postcode}");
Ok(Json(PostcodeLookupResponse {
postcode: normalized,
latitude: lat as f64,
longitude: lon as f64,
vertices,
}))
Ok(Json(serde_json::json!({
"postcode": normalized,
"latitude": lat as f64,
"longitude": lon as f64,
"geometry": geometry,
})))
} else {
Err(StatusCode::NOT_FOUND)
}