This commit is contained in:
Andras Schmelczer 2026-02-02 20:10:32 +00:00
parent 9179acd4cd
commit 2c613dc0d1
14 changed files with 376 additions and 188 deletions

View file

@ -1,15 +1,20 @@
/// 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.
/// Divides the bounding box into cells of ~0.01 degrees (~1km).
/// Uses a Compressed Sparse Row (CSR) layout: a single flat `values` array
/// plus an `offsets` array so that cell `i` owns `values[offsets[i]..offsets[i+1]]`.
/// This eliminates per-cell Vec overhead (24 bytes each for ptr+len+cap).
pub struct GridIndex {
min_lat: f32,
min_lon: f32,
cell_size: f32,
cols: usize,
rows: usize,
/// cells[row * cols + col] = vec of row indices
cells: Vec<Vec<u32>>,
/// Flat array of row indices, grouped by cell.
values: Vec<u32>,
/// offsets[i] is the start index in `values` for cell i.
/// offsets[num_cells] is values.len() (sentinel).
offsets: Vec<u32>,
}
impl GridIndex {
@ -41,25 +46,47 @@ impl GridIndex {
let rows = ((max_lat - min_lat) / cell_size).ceil() as usize + 1;
let cols = ((max_lon - min_lon) / cell_size).ceil() as usize + 1;
let num_cells = rows * cols;
tracing::debug!(
rows_grid = rows,
cols_grid = cols,
total_cells = rows * cols,
total_cells = num_cells,
cell_size,
"Building grid index"
"Building grid index (CSR)"
);
let mut cells: Vec<Vec<u32>> = vec![Vec::new(); rows * cols];
// First pass: count items per cell
let mut counts = vec![0u32; num_cells];
for index in 0..lat.len() {
let grid_row = ((lat[index] - min_lat) / cell_size) as usize;
let grid_col = ((lon[index] - min_lon) / cell_size) as usize;
counts[grid_row * cols + grid_col] += 1;
}
// Build offsets from counts (prefix sum)
let mut offsets = Vec::with_capacity(num_cells + 1);
let mut running = 0u32;
for &count in &counts {
offsets.push(running);
running += count;
}
offsets.push(running);
let total = running as usize;
// Second pass: fill values using write cursors
let mut cursors = offsets[..num_cells].to_vec();
let mut values = vec![0u32; total];
for index in 0..lat.len() {
let grid_row = ((lat[index] - min_lat) / cell_size) as usize;
let grid_col = ((lon[index] - min_lon) / cell_size) as usize;
let cell_index = grid_row * cols + grid_col;
cells[cell_index].push(index as u32);
let pos = cursors[cell_index] as usize;
values[pos] = index as u32;
cursors[cell_index] += 1;
}
tracing::debug!("Grid index built");
tracing::debug!("Grid index built (CSR)");
GridIndex {
min_lat,
@ -67,7 +94,8 @@ impl GridIndex {
cell_size,
cols,
rows,
cells,
values,
offsets,
}
}
@ -83,7 +111,10 @@ impl GridIndex {
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]);
let cell_idx = row_start + col;
let start = self.offsets[cell_idx] as usize;
let end = self.offsets[cell_idx + 1] as usize;
result.extend_from_slice(&self.values[start..end]);
}
}
@ -108,7 +139,10 @@ impl GridIndex {
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] {
let cell_idx = row_start + col;
let start = self.offsets[cell_idx] as usize;
let end = self.offsets[cell_idx + 1] as usize;
for &row_idx in &self.values[start..end] {
callback(row_idx);
}
}