use core::fmt;
use serde::Serialize;
pub const EARTH_RADIUS_KM: f64 = 6371.0;
pub fn haversine_km(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
let dlat = (lat2 - lat1).to_radians();
let dlon = (lon2 - lon1).to_radians();
let a = (dlat / 2.0).sin().powi(2)
+ lat1.to_radians().cos() * lat2.to_radians().cos() * (dlon / 2.0).sin().powi(2);
EARTH_RADIUS_KM * 2.0 * a.sqrt().asin()
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct DataInfo {
pub source: String,
pub decree: String,
pub village_count: u32,
pub build_date: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Village {
pub code: String,
pub name: String,
pub district: String,
pub city: String,
pub province: String,
pub lat: f64,
pub lon: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub dist_km: Option<f64>,
}
impl fmt::Display for Village {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} — {}, {}, {} ({})",
self.name, self.district, self.city, self.province, self.code
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum LocateMethod {
Nearest,
Contained,
}
impl fmt::Display for LocateMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LocateMethod::Nearest => write!(f, "nearest"),
LocateMethod::Contained => write!(f, "contained"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct AdminLevel {
pub code: String,
pub name: String,
}
impl fmt::Display for AdminLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.code, self.name)
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Location {
pub province: AdminLevel,
pub city: AdminLevel,
pub district: AdminLevel,
pub village: String,
pub village_code: String,
pub lat: f64,
pub lon: f64,
pub dist_km: f64,
pub method: LocateMethod,
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}", self.province)?;
writeln!(f, " {}", self.city)?;
writeln!(f, " {}", self.district)?;
writeln!(
f,
" {} {} ({:.1} km, {})",
self.village_code, self.village, self.dist_km, self.method
)
}
}
pub fn location_from_village(v: &Village, dist_km: f64) -> Option<Location> {
let parts: Vec<&str> = v.code.split('.').collect();
if parts.len() != 4 {
return None;
}
Some(Location {
province: AdminLevel {
code: parts[0].to_string(),
name: v.province.clone(),
},
city: AdminLevel {
code: format!("{}.{}", parts[0], parts[1]),
name: v.city.clone(),
},
district: AdminLevel {
code: format!("{}.{}.{}", parts[0], parts[1], parts[2]),
name: v.district.clone(),
},
village: v.name.clone(),
village_code: v.code.clone(),
lat: v.lat,
lon: v.lon,
dist_km,
method: LocateMethod::Nearest,
})
}
#[derive(Debug, Clone, Serialize)]
pub enum LookupResult {
Found(Village),
Ambiguous(Vec<Village>),
NotFound,
}
impl fmt::Display for LookupResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LookupResult::Found(v) => write!(f, "{}", v),
LookupResult::Ambiguous(list) => {
writeln!(f, "Found {} matching villages:", list.len())?;
for (i, v) in list.iter().enumerate() {
writeln!(f, " {}. {}", i + 1, v)?;
}
write!(
f,
"Use a more specific query (e.g., include city or province)"
)
}
LookupResult::NotFound => write!(f, "No matching village found"),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PrefixResult {
pub villages: Vec<Village>,
pub total: usize,
pub has_more: bool,
}
impl fmt::Display for PrefixResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} result(s), total: {}, has_more: {}",
self.villages.len(),
self.total,
self.has_more,
)
}
}