Support outcode & gps search
This commit is contained in:
parent
23d128ff63
commit
3853b5dce7
6 changed files with 188 additions and 3 deletions
|
|
@ -6,6 +6,94 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use super::PlaceData;
|
||||
|
||||
/// Precomputed outcode data derived from postcode boundaries.
|
||||
/// An outcode is the first part of a UK postcode (e.g. "E14" from "E14 2DG").
|
||||
pub struct OutcodeData {
|
||||
pub names: Vec<String>,
|
||||
pub name_lower: Vec<String>,
|
||||
pub centroids: Vec<(f32, f32)>,
|
||||
pub cities: Vec<Option<String>>,
|
||||
}
|
||||
|
||||
impl OutcodeData {
|
||||
/// Derive outcode data by grouping postcodes by their outcode prefix and averaging centroids.
|
||||
pub fn from_postcode_and_place_data(
|
||||
postcode_data: &PostcodeData,
|
||||
place_data: &PlaceData,
|
||||
) -> Self {
|
||||
// Group postcode centroids by outcode
|
||||
let mut outcode_centroids: FxHashMap<String, Vec<(f32, f32)>> = FxHashMap::default();
|
||||
for (idx, postcode) in postcode_data.postcodes.iter().enumerate() {
|
||||
if let Some(space_idx) = postcode.find(' ') {
|
||||
let outcode = &postcode[..space_idx];
|
||||
outcode_centroids
|
||||
.entry(outcode.to_string())
|
||||
.or_default()
|
||||
.push(postcode_data.centroids[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build sorted vecs
|
||||
let mut entries: Vec<(String, (f32, f32))> = outcode_centroids
|
||||
.into_iter()
|
||||
.map(|(outcode, pts)| {
|
||||
let count = pts.len() as f32;
|
||||
let avg_lat = pts.iter().map(|(lat, _)| lat).sum::<f32>() / count;
|
||||
let avg_lon = pts.iter().map(|(_, lon)| lon).sum::<f32>() / count;
|
||||
(outcode, (avg_lat, avg_lon))
|
||||
})
|
||||
.collect();
|
||||
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let names: Vec<String> = entries.iter().map(|(n, _)| n.clone()).collect();
|
||||
let name_lower: Vec<String> = names.iter().map(|n| n.to_lowercase()).collect();
|
||||
let centroids: Vec<(f32, f32)> = entries.iter().map(|(_, c)| *c).collect();
|
||||
|
||||
// Compute nearest city for each outcode (same algorithm as PlaceData)
|
||||
let city_indices: Vec<usize> = place_data
|
||||
.type_rank
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, &rank)| if rank == 0 { Some(idx) } else { None })
|
||||
.collect();
|
||||
|
||||
let cities: Vec<Option<String>> = centroids
|
||||
.iter()
|
||||
.map(|&(lat, lon)| {
|
||||
let cos_lat = lat.to_radians().cos();
|
||||
let mut best_dist_sq = f32::MAX;
|
||||
let mut best_city: Option<&str> = None;
|
||||
for &ci in &city_indices {
|
||||
let dlat = place_data.lat[ci] - lat;
|
||||
let dlon = (place_data.lon[ci] - lon) * cos_lat;
|
||||
let dist_sq = dlat * dlat + dlon * dlon;
|
||||
if dist_sq < best_dist_sq {
|
||||
best_dist_sq = dist_sq;
|
||||
best_city = Some(&place_data.name[ci]);
|
||||
}
|
||||
}
|
||||
// ~100km threshold
|
||||
if best_dist_sq < 0.81 {
|
||||
best_city.map(|s| s.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
info!(outcodes = names.len(), "Outcode data derived from postcodes");
|
||||
|
||||
OutcodeData {
|
||||
names,
|
||||
name_lower,
|
||||
centroids,
|
||||
cities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GeoJSON structures for parsing postcode boundary files
|
||||
#[derive(Deserialize)]
|
||||
struct FeatureCollection {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue