Support outcode & gps search
This commit is contained in:
parent
23d128ff63
commit
3853b5dce7
6 changed files with 188 additions and 3 deletions
|
|
@ -52,6 +52,7 @@ pub async fn get_places(
|
|||
let t0 = std::time::Instant::now();
|
||||
let query_lower = query.to_lowercase();
|
||||
let pd = &state.place_data;
|
||||
let od = &state.outcode_data;
|
||||
let tt_store = &state.travel_time_store;
|
||||
|
||||
// Linear scan — ~50-100k rows, <1ms
|
||||
|
|
@ -99,7 +100,7 @@ pub async fn get_places(
|
|||
|
||||
matches.truncate(limit);
|
||||
|
||||
let results: Vec<PlaceResult> = matches
|
||||
let mut results: Vec<PlaceResult> = matches
|
||||
.iter()
|
||||
.map(|(idx, .., slug)| PlaceResult {
|
||||
name: pd.name[*idx].clone(),
|
||||
|
|
@ -111,6 +112,49 @@ pub async fn get_places(
|
|||
})
|
||||
.collect();
|
||||
|
||||
// Also search outcodes (skip when mode filter is set — outcodes aren't travel destinations)
|
||||
if mode_filter.is_none() {
|
||||
let query_upper = query_lower.to_uppercase();
|
||||
let mut outcode_results: Vec<PlaceResult> = od
|
||||
.name_lower
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, name)| {
|
||||
if !name.starts_with(&query_lower) {
|
||||
return None;
|
||||
}
|
||||
let is_exact = name.len() == query_lower.len();
|
||||
Some((idx, is_exact))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(|(idx, _is_exact)| PlaceResult {
|
||||
name: od.names[idx].clone(),
|
||||
slug: od.names[idx].to_lowercase(),
|
||||
place_type: "outcode".to_string(),
|
||||
lat: od.centroids[idx].0,
|
||||
lon: od.centroids[idx].1,
|
||||
city: od.cities[idx].clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort outcodes: exact first, then by name length (shorter = broader area)
|
||||
outcode_results.sort_unstable_by(|a, b| {
|
||||
let a_exact = a.name.eq_ignore_ascii_case(&query_upper);
|
||||
let b_exact = b.name.eq_ignore_ascii_case(&query_upper);
|
||||
b_exact
|
||||
.cmp(&a_exact)
|
||||
.then(a.name.len().cmp(&b.name.len()))
|
||||
});
|
||||
|
||||
// Prepend outcode results (up to 3) before place results, keeping total ≤ limit
|
||||
outcode_results.truncate(3);
|
||||
let place_slots = limit.saturating_sub(outcode_results.len());
|
||||
results.truncate(place_slots);
|
||||
outcode_results.append(&mut results);
|
||||
results = outcode_results;
|
||||
}
|
||||
|
||||
let elapsed = t0.elapsed();
|
||||
info!(
|
||||
query = query.as_str(),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ pub struct PostcodesResponse {
|
|||
features: Vec<Map<String, Value>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NearestPostcodeParams {
|
||||
lat: f64,
|
||||
lng: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PostcodeParams {
|
||||
bounds: Option<String>,
|
||||
|
|
@ -311,6 +317,45 @@ pub async fn get_postcodes(
|
|||
Ok(Json(response))
|
||||
}
|
||||
|
||||
/// Find the nearest postcode to a given lat/lng coordinate.
|
||||
pub async fn get_nearest_postcode(
|
||||
State(shared): State<Arc<SharedState>>,
|
||||
Query(params): Query<NearestPostcodeParams>,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
let state = shared.load_state();
|
||||
let postcode_data = &state.postcode_data;
|
||||
|
||||
let query_lat = params.lat as f32;
|
||||
let query_lng = params.lng as f32;
|
||||
let cos_lat = (query_lat as f64).to_radians().cos() as f32;
|
||||
|
||||
let mut best_idx: Option<usize> = None;
|
||||
let mut best_dist_sq = f32::MAX;
|
||||
|
||||
for (idx, &(pc_lat, pc_lon)) in postcode_data.centroids.iter().enumerate() {
|
||||
let dlat = pc_lat - query_lat;
|
||||
let dlon = (pc_lon - query_lng) * cos_lat;
|
||||
let dist_sq = dlat * dlat + dlon * dlon;
|
||||
if dist_sq < best_dist_sq {
|
||||
best_dist_sq = dist_sq;
|
||||
best_idx = Some(idx);
|
||||
}
|
||||
}
|
||||
|
||||
let idx = best_idx.ok_or(StatusCode::NOT_FOUND)?;
|
||||
let (lat, lon) = postcode_data.centroids[idx];
|
||||
let geometry = postcode_data.geometries[idx].clone();
|
||||
let postcode = &postcode_data.postcodes[idx];
|
||||
|
||||
info!(postcode = %postcode, "GET /api/nearest-postcode");
|
||||
Ok(Json(serde_json::json!({
|
||||
"postcode": postcode,
|
||||
"latitude": lat as f64,
|
||||
"longitude": lon as f64,
|
||||
"geometry": geometry,
|
||||
})))
|
||||
}
|
||||
|
||||
/// Look up a single postcode and return its centroid coordinates and geometry.
|
||||
pub async fn get_postcode_lookup(
|
||||
State(shared): State<Arc<SharedState>>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue