use geo::{HaversineDistance, Point};
use super::value::{house_number, house_number_shape};
const NEAREST_MAX_DISTANCE_IN_METERS: f64 = 50.0;
#[derive(Debug, PartialEq)]
pub enum house_number_resolution {
exact(Point<f64>),
interpolated(Point<f64>),
absent,
}
pub fn resolve(
wanted: &house_number,
known: &[(house_number, Point<f64>)],
) -> house_number_resolution {
if let Some((_, point)) = known.iter().find(|(number, _)| number == wanted) {
return house_number_resolution::exact(*point);
}
if wanted.shape() == house_number_shape::compound {
return house_number_resolution::absent;
}
match wanted
.leading_value()
.and_then(|target| interpolate(known, target))
{
Some(point) => house_number_resolution::interpolated(point),
None => house_number_resolution::absent,
}
}
fn interpolate(known: &[(house_number, Point<f64>)], target: u32) -> Option<Point<f64>> {
let mut below: Option<(u32, Point<f64>)> = None;
let mut above: Option<(u32, Point<f64>)> = None;
for (number, point) in known {
let Some(value) = number.leading_value() else {
continue;
};
if value < target {
if below.is_none_or(|(current, _)| value > current) {
below = Some((value, *point));
}
} else if value > target && above.is_none_or(|(current, _)| value < current) {
above = Some((value, *point));
}
}
let ((low, low_point), (high, high_point)) = (below?, above?);
let fraction = (target - low) as f64 / (high - low) as f64;
Some(Point::new(
low_point.x() + (high_point.x() - low_point.x()) * fraction,
low_point.y() + (high_point.y() - low_point.y()) * fraction,
))
}
pub fn nearest(point: Point<f64>, known: &[(house_number, Point<f64>)]) -> Option<&house_number> {
known
.iter()
.filter_map(|(number, at)| {
let distance = point.haversine_distance(at);
(distance <= NEAREST_MAX_DISTANCE_IN_METERS).then_some((number, distance))
})
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(number, _)| number)
}