geolite 0.0.17

open source geocode solution
use super::entity::query_match;
use super::query_opts;

pub(super) const MAX_RESULTS: u8 = 10;
const COORDINATE_QUALITY_REFERENCE_M: f64 = 100.0;

// the cut at MAX_RESULTS comes after every filter, so no filter discards a match that would have
// made the cut
pub(super) fn apply_filters_and_truncate(matches: &mut Vec<query_match>, opts: &query_opts) {
  if let Some(threshold) = opts.min_quality {
    matches.retain(|m| match_quality(m) >= threshold);
  }
  if let Some(b) = opts.bounding.as_ref() {
    // the rtree only tested the envelope, which does not imply the match's final point is inside
    // the geometry; this exact containment is the authoritative one
    matches.retain(|m| b.contains(m.latitude, m.longitude));
  }
  matches.truncate(MAX_RESULTS as usize);
}

pub fn parse_min_quality(text: &str) -> Result<f64, String> {
  let value: f64 = text
    .trim()
    .parse()
    .map_err(|e| format!("quality: not a number: {e}"))?;
  if !(0.0..=1.0).contains(&value) {
    return Err(format!("quality: must be between 0.0 and 1.0, got {value}"));
  }
  Ok(value)
}

pub(super) fn coordinate_quality(distance_in_meters: Option<u32>) -> f64 {
  match distance_in_meters {
    Some(d) => (1.0 - d as f64 / COORDINATE_QUALITY_REFERENCE_M).clamp(0.0, 1.0),
    None => 1.0,
  }
}

fn match_quality(m: &query_match) -> f64 {
  if let Some(s) = m.similarity {
    return s as f64;
  }
  coordinate_quality(m.coordinates_distance_in_meters)
}