use csv::Reader;
use kiddo::float::{distance::SquaredEuclidean, kdtree::KdTree};
use serde::Deserialize;
use std::error::Error;
use std::fmt::Formatter;
use std::fs::File;
#[allow(dead_code)]
pub const EARTH_RADIUS_IN_KM: f32 = 6371.0;
#[derive(Debug, Deserialize)]
pub struct CityCsvRecord {
#[allow(dead_code)]
#[serde(rename = "city")]
name: String,
lat: f32,
lng: f32,
country: String,
population: u32,
}
impl std::fmt::Display for CityCsvRecord {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}, {} (pop. {})",
self.name, self.country, self.population
)
}
}
impl CityCsvRecord {
pub fn as_xyz(&self) -> [f32; 3] {
degrees_lat_lng_to_unit_sphere(self.lat, self.lng)
}
}
pub fn degrees_lat_lng_to_unit_sphere(lat: f32, lng: f32) -> [f32; 3] {
let lat = lat.to_radians();
let lng = lng.to_radians();
[lat.cos() * lng.cos(), lat.cos() * lng.sin(), lat.sin()]
}
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn Error>> {
let cities: Vec<CityCsvRecord> = parse_csv_file("./examples/worldcities.csv")?;
let mut kdtree: KdTree<f32, usize, 3, 32, u16> = KdTree::with_capacity(cities.len());
cities.iter().enumerate().for_each(|(idx, city)| {
kdtree.add(&city.as_xyz(), idx);
});
println!("Loaded {} items into Kiddo k-d tree", kdtree.size());
let query = degrees_lat_lng_to_unit_sphere(52.5f32, -1.9f32);
let nearest_neighbour = kdtree.nearest_one::<SquaredEuclidean>(&query);
let nearest = &cities[nearest_neighbour.item as usize];
println!(
"\nNearest city to 52.5N, 1.9W: {} ({:.1})km",
nearest, nearest_neighbour.distance
);
let query = degrees_lat_lng_to_unit_sphere(52.5f32, -1.9f32);
let nearest_5_idx = kdtree.nearest_n::<SquaredEuclidean>(&query, 5);
let nearest_5 = nearest_5_idx
.into_iter()
.map(|neighbour| {
(
&cities[neighbour.item].name,
format!(
"{dist:.1}km",
dist = unit_sphere_squared_euclidean_to_kilometres(neighbour.distance)
),
)
})
.collect::<Vec<_>>();
println!("\nNearest 5 cities to 52.5N, 1.9W: {:?}", nearest_5);
let query = degrees_lat_lng_to_unit_sphere(0f32, 0f32);
let dist = kilometres_to_unit_sphere_squared_euclidean(1000.0);
let all_within = kdtree
.within::<SquaredEuclidean>(&query, dist)
.iter()
.map(|neighbour| &cities[neighbour.item].name)
.collect::<Vec<_>>();
println!("\nAll cities within 1000km of 0N, 0W: {:?}", all_within);
let query = degrees_lat_lng_to_unit_sphere(0f32, 0f32);
let dist = kilometres_to_unit_sphere_squared_euclidean(1000.0);
let best_3_iter = kdtree.best_n_within::<SquaredEuclidean>(&query, dist, 3);
let best_3 = best_3_iter
.map(|neighbour| (&cities[neighbour.item].name))
.collect::<Vec<_>>();
println!(
"\nMost populous 3 cities within 1000km of 0N, 0W: {:?}",
best_3
);
Ok(())
}
#[allow(dead_code)]
pub fn unit_sphere_squared_euclidean_to_kilometres(sq_euc_dist: f32) -> f32 {
sq_euc_dist.sqrt() * EARTH_RADIUS_IN_KM
}
pub fn kilometres_to_unit_sphere_squared_euclidean(km_dist: f32) -> f32 {
(km_dist / EARTH_RADIUS_IN_KM).powi(2)
}
pub fn parse_csv_file<R: for<'de> serde::Deserialize<'de>>(
filename: &str,
) -> Result<Vec<R>, std::io::Error> {
let file = File::open(filename)?;
let cities: Vec<R> = Reader::from_reader(file)
.deserialize()
.filter_map(Result::ok)
.collect();
println!("Cities successfully parsed from CSV: {:?}", cities.len());
Ok(cities)
}