Format rust

This commit is contained in:
Andras Schmelczer 2026-01-31 13:57:43 +00:00
parent 0fde087c3d
commit f60fbec9d4
5 changed files with 191 additions and 94 deletions

View file

@ -8,7 +8,8 @@ use axum::response::{IntoResponse, Json};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use crate::data::{Histogram, PropertyData, POIData, POI, DEFAULT_RESOLUTION, MAX_RESOLUTION, MIN_RESOLUTION};
use crate::consts::{H3_PRECOMPUTE_MAX, H3_PRECOMPUTE_MIN};
use crate::data::{Histogram, POIData, PropertyData, POI};
use crate::index::GridIndex;
/// Shared application state
@ -82,7 +83,7 @@ pub async fn get_features(state: Arc<AppState>) -> Json<FeaturesResponse> {
#[derive(Deserialize)]
pub struct HexagonParams {
resolution: Option<u8>,
resolution: u8,
bounds: Option<String>,
/// Comma-separated filters: `name:min:max,...`
/// Rows must have non-NaN values within [min,max] for each filter.
@ -130,7 +131,6 @@ impl CellAgg {
}
}
}
}
/// Write the hexagons JSON response directly to a String buffer,
@ -172,20 +172,21 @@ pub async fn get_hexagons(
state: Arc<AppState>,
Query(params): Query<HexagonParams>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let resolution = params.resolution.unwrap_or(DEFAULT_RESOLUTION);
if resolution > MAX_RESOLUTION {
let resolution = params.resolution;
if resolution < H3_PRECOMPUTE_MIN || resolution > H3_PRECOMPUTE_MAX {
return Err((
StatusCode::BAD_REQUEST,
format!(
"resolution must be between {} and {}",
MIN_RESOLUTION, MAX_RESOLUTION
H3_PRECOMPUTE_MIN, H3_PRECOMPUTE_MAX
),
));
}
let bounds_str = params
.bounds
.ok_or((StatusCode::BAD_REQUEST, "bounds parameter is required".into()))?;
let bounds_str = params.bounds.ok_or((
StatusCode::BAD_REQUEST,
"bounds parameter is required".into(),
))?;
let parts: Vec<f64> = bounds_str
.split(',')
@ -286,46 +287,44 @@ pub async fn get_hexagons(
if let Some(precomputed) = h3_cells_for_res {
// Fast path: precomputed H3 + visitor pattern
state.grid.for_each_in_bounds(south, west, north, east, |row_idx| {
let row = row_idx as usize;
if !row_passes(row) {
return;
}
let cell_id = precomputed[row];
groups
.entry(cell_id)
.or_insert_with(|| CellAgg::new(num_features))
.add_row(feature_data, row, num_features);
});
state
.grid
.for_each_in_bounds(south, west, north, east, |row_idx| {
let row = row_idx as usize;
if !row_passes(row) {
return;
}
let cell_id = precomputed[row];
groups
.entry(cell_id)
.or_insert_with(|| CellAgg::new(num_features))
.add_row(feature_data, row, num_features);
});
} else {
// Fallback: compute H3 on-the-fly
let h3_res = h3o::Resolution::try_from(resolution).unwrap();
state.grid.for_each_in_bounds(south, west, north, east, |row_idx| {
let row = row_idx as usize;
if !row_passes(row) {
return;
}
let cell_id = h3o::LatLng::new(state.data.lat[row], state.data.lon[row])
.map(|c| u64::from(c.to_cell(h3_res)))
.unwrap_or(0);
groups
.entry(cell_id)
.or_insert_with(|| CellAgg::new(num_features))
.add_row(feature_data, row, num_features);
});
state
.grid
.for_each_in_bounds(south, west, north, east, |row_idx| {
let row = row_idx as usize;
if !row_passes(row) {
return;
}
let cell_id = h3o::LatLng::new(state.data.lat[row], state.data.lon[row])
.map(|c| u64::from(c.to_cell(h3_res)))
.unwrap_or(0);
groups
.entry(cell_id)
.or_insert_with(|| CellAgg::new(num_features))
.add_row(feature_data, row, num_features);
});
}
let t_agg = t0.elapsed();
// Write JSON directly (no serde_json::Value allocation overhead)
let mut json_buf = String::with_capacity(groups.len() * 128);
write_hexagons_json(
&mut json_buf,
&groups,
&min_keys,
&max_keys,
num_features,
);
write_hexagons_json(&mut json_buf, &groups, &min_keys, &max_keys, num_features);
let t_total = t0.elapsed();
eprintln!(
@ -364,9 +363,10 @@ pub async fn get_pois(
state: Arc<AppState>,
Query(params): Query<POIParams>,
) -> Result<Json<POIsResponse>, (StatusCode, String)> {
let bounds_str = params
.bounds
.ok_or((StatusCode::BAD_REQUEST, "bounds parameter is required".into()))?;
let bounds_str = params.bounds.ok_or((
StatusCode::BAD_REQUEST,
"bounds parameter is required".into(),
))?;
let parts: Vec<f64> = bounds_str
.split(',')
@ -501,7 +501,12 @@ pub struct HexagonPropertiesResponse {
}
/// Helper function to check if a row passes all filters
fn row_passes_filters(row: usize, filters: &[ParsedFilter], feature_data: &[f64], num_features: usize) -> bool {
fn row_passes_filters(
row: usize,
filters: &[ParsedFilter],
feature_data: &[f64],
num_features: usize,
) -> bool {
filters.iter().all(|f| {
let v = feature_data[row * num_features + f.feat_idx];
v.is_finite() && v >= f.min && v <= f.max
@ -520,7 +525,10 @@ pub async fn get_hexagon_properties(
// 2. Validate resolution
let resolution = params.resolution as usize;
if resolution >= state.h3_cells.len() || state.h3_cells[resolution].is_empty() {
return Err((StatusCode::BAD_REQUEST, "Invalid or non-precomputed resolution".to_string()));
return Err((
StatusCode::BAD_REQUEST,
"Invalid or non-precomputed resolution".to_string(),
));
}
// 3. Parse filters (reuse existing filter parsing logic from get_hexagons)
@ -592,7 +600,11 @@ pub async fn get_hexagon_properties(
// Helper to get non-empty string
let get_string = |s: &str| -> Option<String> {
if s.is_empty() { None } else { Some(s.to_string()) }
if s.is_empty() {
None
} else {
Some(s.to_string())
}
};
Property {