perfect-postcode/server-rs/src/routes/travel_time.rs
2026-03-14 21:36:00 +00:00

96 lines
2.8 KiB
Rust

/// Parse the optional `travel` query param, returning an empty Vec when absent or empty.
pub fn parse_optional_travel(travel: Option<&str>) -> Result<Vec<TravelEntry>, String> {
match travel.filter(|val| !val.is_empty()) {
Some(s) => parse_travel_entries(s),
None => Ok(Vec::new()),
}
}
/// A parsed travel time entry from the `travel` query parameter.
pub struct TravelEntry {
pub mode: String,
pub slug: String,
pub use_best: bool,
pub filter_min: Option<f32>,
pub filter_max: Option<f32>,
}
/// Parse `travel` param into a list of travel entries.
/// Format: `mode:slug` or `mode:slug:best` or `mode:slug:min:max` or `mode:slug:best:min:max`
fn parse_travel_entries(travel_str: &str) -> Result<Vec<TravelEntry>, String> {
let mut entries = Vec::new();
let mut seen_keys = Vec::new();
for segment in travel_str.split('|') {
let parts: Vec<&str> = segment.split(':').collect();
if parts.len() < 2 {
return Err(format!(
"each travel entry must be 'mode:slug' or 'mode:slug:min:max', got '{}'",
segment
));
}
let mode = parts[0].trim().to_string();
let slug = parts[1].trim().to_string();
let use_best = parts.len() >= 3 && parts[2].trim() == "best";
let filter_offset = if use_best { 1 } else { 0 };
let (filter_min, filter_max) = if parts.len() >= 4 + filter_offset {
let min: f32 = parts[2 + filter_offset]
.trim()
.parse()
.map_err(|_| format!("invalid travel filter min in '{}'", segment))?;
let max: f32 = parts[3 + filter_offset]
.trim()
.parse()
.map_err(|_| format!("invalid travel filter max in '{}'", segment))?;
(Some(min), Some(max))
} else {
(None, None)
};
let key = format!("{}:{}", mode, slug);
if seen_keys.contains(&key) {
return Err(format!("duplicate travel entry '{}'", key));
}
seen_keys.push(key);
entries.push(TravelEntry {
mode,
slug,
use_best,
filter_min,
filter_max,
});
}
Ok(entries)
}
/// Per-cell travel time aggregation.
pub struct TravelTimeAgg {
pub min: f32,
pub max: f32,
pub sum: f64,
pub count: u32,
}
impl TravelTimeAgg {
pub fn new() -> Self {
TravelTimeAgg {
min: f32::INFINITY,
max: f32::NEG_INFINITY,
sum: 0.0,
count: 0,
}
}
#[inline]
pub fn add(&mut self, value: f32) {
if value < self.min {
self.min = value;
}
if value > self.max {
self.max = value;
}
self.sum += value as f64;
self.count += 1;
}
}