This commit is contained in:
Andras Schmelczer 2026-04-04 17:44:44 +01:00
parent b94cf17d75
commit 0c6d207967
41 changed files with 1809 additions and 1204 deletions

View file

@ -11,14 +11,14 @@ use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use tracing::info;
use crate::aggregation::Aggregator;
use crate::aggregation::{Aggregator, EnumDistConfig};
use crate::auth::OptionalUser;
use crate::consts::MAX_CELLS_PER_REQUEST;
use crate::data::travel_time::TravelData;
use crate::licensing::check_license_bounds;
use crate::parsing::{
cell_for_row_cached, needs_parent, parse_field_indices, parse_filters, require_bounds,
row_passes_filters, validate_h3_resolution,
cell_for_row_cached, needs_parent, parse_enum_dist, parse_field_indices, parse_filters,
require_bounds, row_passes_filters, validate_h3_resolution,
};
use crate::routes::travel_time::{parse_optional_travel, TravelTimeAgg};
use crate::state::SharedState;
@ -65,6 +65,9 @@ pub struct HexagonParams {
/// Each entry requests travel time aggregation for that mode+destination.
/// Optional min:max applies as a filter (exclude properties outside range).
travel: Option<String>,
/// Feature name for enum distribution counting (pie chart visualization).
/// When set, each cell includes `dist_{name}: [count_val0, count_val1, ...]`.
enum_dist: Option<String>,
}
/// Build feature maps from aggregated cell data, filtering to only cells whose
@ -83,6 +86,7 @@ fn build_feature_maps(
resolution: h3o::Resolution,
travel_aggs: &[FxHashMap<u64, TravelTimeAgg>],
travel_field_keys: &[String],
enum_dist_key: Option<&str>,
) -> Vec<Map<String, Value>> {
let mut features = Vec::with_capacity(groups.len());
let (q_south, q_west, q_north, q_east) = query_bounds;
@ -175,6 +179,12 @@ fn build_feature_maps(
}
}
// Add enum distribution array (for pie chart visualization)
if let (Some(key), Some(ref ed)) = (enum_dist_key, &aggregation.enum_dist) {
let arr: Vec<Value> = ed.counts.iter().map(|&c| Value::from(c)).collect();
map.insert(key.to_string(), Value::Array(arr));
}
features.push(map);
}
@ -212,6 +222,19 @@ pub async fn get_hexagons(
let travel_entries = parse_optional_travel(params.travel.as_deref())
.map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?;
let enum_dist_config: EnumDistConfig = parse_enum_dist(
params.enum_dist.as_deref(),
&state.feature_name_to_index,
&state.data.enum_values,
)
.map_err(|err| (err.0, err.1).into_response())?;
// Pre-compute the dist_ key name (e.g. "dist_Property type") outside spawn_blocking
let enum_dist_key: Option<String> = params
.enum_dist
.as_ref()
.map(|name| format!("dist_{}", name.trim()));
let response = tokio::task::spawn_blocking(move || -> Result<HexagonsResponse, String> {
let t0 = std::time::Instant::now();
@ -325,7 +348,7 @@ pub async fn get_hexagons(
let agg = local_groups
.entry(cell_id)
.or_insert_with(|| Aggregator::new(num_features));
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config));
if let Some(sel_indices) = field_indices.as_deref() {
agg.add_row_selective(
feature_data,
@ -357,7 +380,7 @@ pub async fn get_hexagons(
for (cell_id, local_agg) in local_groups {
groups
.entry(cell_id)
.or_insert_with(|| Aggregator::new(num_features))
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config))
.merge(&local_agg);
}
for (ti, local_ta) in local_travel.into_iter().enumerate() {
@ -417,7 +440,7 @@ pub async fn get_hexagons(
let aggregation = groups
.entry(cell_id)
.or_insert_with(|| Aggregator::new(num_features));
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config));
if let Some(sel_indices) = field_indices.as_deref() {
aggregation.add_row_selective(
feature_data,
@ -454,6 +477,7 @@ pub async fn get_hexagons(
h3_res,
&travel_aggs,
&travel_field_keys,
enum_dist_key.as_deref(),
);
let truncated = features.len() > MAX_CELLS_PER_REQUEST;