Refactor parts of the server

This commit is contained in:
Andras Schmelczer 2026-02-01 13:59:07 +00:00
parent ccb7c8fbd7
commit 47de4e563f
6 changed files with 369 additions and 90 deletions

147
server-rs/src/grid_index.rs Normal file
View file

@ -0,0 +1,147 @@
/// Grid-based spatial index for fast rectangle queries over property rows.
///
/// Divides the UK bounding box into cells of ~0.01 degrees (~1km),
/// each storing indices of rows whose lat/lon falls within that cell.
pub struct GridIndex {
min_lat: f64,
min_lon: f64,
cell_size: f64,
cols: usize,
rows: usize,
/// cells[row * cols + col] = vec of row indices
cells: Vec<Vec<u32>>,
}
impl GridIndex {
pub fn build(lat: &[f64], lon: &[f64], cell_size: f64) -> Self {
let mut min_lat = f64::INFINITY;
let mut max_lat = f64::NEG_INFINITY;
let mut min_lon = f64::INFINITY;
let mut max_lon = f64::NEG_INFINITY;
for i in 0..lat.len() {
if lat[i] < min_lat {
min_lat = lat[i];
}
if lat[i] > max_lat {
max_lat = lat[i];
}
if lon[i] < min_lon {
min_lon = lon[i];
}
if lon[i] > max_lon {
max_lon = lon[i];
}
}
min_lat -= cell_size;
min_lon -= cell_size;
max_lat += cell_size;
max_lon += cell_size;
let rows = ((max_lat - min_lat) / cell_size).ceil() as usize + 1;
let cols = ((max_lon - min_lon) / cell_size).ceil() as usize + 1;
tracing::debug!(
rows_grid = rows,
cols_grid = cols,
total_cells = rows * cols,
cell_size,
"Building grid index"
);
let mut cells: Vec<Vec<u32>> = vec![Vec::new(); rows * cols];
for i in 0..lat.len() {
let grid_row = ((lat[i] - min_lat) / cell_size) as usize;
let grid_col = ((lon[i] - min_lon) / cell_size) as usize;
let idx = grid_row * cols + grid_col;
cells[idx].push(i as u32);
}
tracing::debug!("Grid index built");
GridIndex {
min_lat,
min_lon,
cell_size,
cols,
rows,
cells,
}
}
pub fn query(&self, south: f64, west: f64, north: f64, east: f64) -> Vec<u32> {
let Some((row_min, row_max, col_min, col_max)) =
self.clamp_bounds(south, west, north, east)
else {
return Vec::new();
};
let mut result = Vec::new();
for row in row_min..=row_max {
let row_start = row * self.cols;
for col in col_min..=col_max {
result.extend_from_slice(&self.cells[row_start + col]);
}
}
result
}
#[inline]
pub fn for_each_in_bounds(
&self,
south: f64,
west: f64,
north: f64,
east: f64,
mut f: impl FnMut(u32),
) {
let Some((row_min, row_max, col_min, col_max)) =
self.clamp_bounds(south, west, north, east)
else {
return;
};
for row in row_min..=row_max {
let row_start = row * self.cols;
for col in col_min..=col_max {
for &row_idx in &self.cells[row_start + col] {
f(row_idx);
}
}
}
}
fn clamp_bounds(
&self,
south: f64,
west: f64,
north: f64,
east: f64,
) -> Option<(usize, usize, usize, usize)> {
let row_min_raw = ((south - self.min_lat) / self.cell_size) as isize;
let row_max_raw = ((north - self.min_lat) / self.cell_size) as isize;
let col_min_raw = ((west - self.min_lon) / self.cell_size) as isize;
let col_max_raw = ((east - self.min_lon) / self.cell_size) as isize;
let row_min = row_min_raw.max(0) as usize;
let row_max_clamped = row_max_raw.min(self.rows as isize - 1);
let col_min = col_min_raw.max(0) as usize;
let col_max_clamped = col_max_raw.min(self.cols as isize - 1);
if row_max_clamped < 0 || col_max_clamped < 0 {
return None;
}
let row_max = row_max_clamped as usize;
let col_max = col_max_clamped as usize;
if row_min > row_max || col_min > col_max {
return None;
}
Some((row_min, row_max, col_min, col_max))
}
}