mod cities;
use std::error::Error;
use std::fs::File;
use std::io::{Read, Write};
use elapsed::ElapsedDuration;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::time::Instant;
use serde::Deserialize;
use cities::{degrees_lat_lng_to_unit_sphere, parse_csv_file};
use kiddo::dist::SquaredEuclidean;
use kiddo::{Eytzinger, KdTree, VecOfArenas, VecOfArrays};
#[derive(Debug, Deserialize)]
pub struct CityCsvRecord {
#[allow(dead_code)]
name: String,
#[serde(rename = "latitude")]
lat: f32,
#[serde(rename = "longitude")]
lng: f32,
}
impl CityCsvRecord {
pub fn as_xyz(&self) -> [f32; 3] {
degrees_lat_lng_to_unit_sphere(self.lat, self.lng)
}
}
const BUCKET_SIZE: usize = 1024;
type Tree = KdTree<f32, usize, Eytzinger, VecOfArrays<f32, usize, 3, BUCKET_SIZE>, 3, BUCKET_SIZE>;
fn main() -> Result<(), Box<dyn Error>> {
let start = Instant::now();
let cities: Vec<CityCsvRecord> = parse_csv_file("./examples/geonames.csv")?;
println!(
"Parsed {} rows from the CSV: ({})",
cities.len(),
ElapsedDuration::new(start.elapsed())
);
let city_entries: Vec<(usize, [f32; 3])> = cities
.iter()
.enumerate()
.map(|(idx, city)| (idx, city.as_xyz()))
.collect();
let start = Instant::now();
let kdtree: Tree = KdTree::new_from_entries(&city_entries)?;
println!(
"Populated k-d tree with {} items ({})",
kdtree.size(),
ElapsedDuration::new(start.elapsed())
);
let query = degrees_lat_lng_to_unit_sphere(52.5f32, -1.9f32);
let nearest_neighbour = kdtree
.query(&query)
.nearest_one::<SquaredEuclidean<f32>>()
.execute();
let nearest_city = &cities[nearest_neighbour.item as usize];
println!("\nNearest city to 52.5N, 1.9W: {nearest_city:?}");
let start = Instant::now();
let file = File::create("./examples/geonames-tree.postcard.gz")?;
let mut encoder = GzEncoder::new(file, Compression::default());
let serialized = postcard::to_allocvec(&kdtree)?;
encoder.write_all(&serialized)?;
encoder.finish()?;
println!(
"Serialized k-d tree to gzipped postcard file ({})",
ElapsedDuration::new(start.elapsed())
);
let start = Instant::now();
let file = File::open("./examples/geonames-tree.postcard.gz")?;
let mut decompressor = GzDecoder::new(file);
let mut bytes = Vec::new();
decompressor.read_to_end(&mut bytes)?;
let deserialized_tree: Tree = postcard::from_bytes(&bytes)?;
println!(
"Deserialized gzipped postcard file back into a k-d tree ({})",
ElapsedDuration::new(start.elapsed())
);
let query = degrees_lat_lng_to_unit_sphere(52.5f32, -1.9f32);
let nearest_neighbour = deserialized_tree
.query(&query)
.nearest_one::<SquaredEuclidean<f32>>()
.execute();
let nearest_city = &cities[nearest_neighbour.item as usize];
println!("\nNearest city to 52.5N, 1.9W: {nearest_city:?}");
let city_points: Vec<_> = cities.iter().map(|city| city.as_xyz()).collect();
println!("Building an ImmutableKdTree...");
let start = Instant::now();
let kdtree: KdTree<f32, u32, Eytzinger, VecOfArenas<f32, u32, 3, 32>, 3, 32> =
KdTree::new_from_slice(&city_points).unwrap();
println!(
"Built an ImmutableKdTree ({})",
ElapsedDuration::new(start.elapsed())
);
let original_nearest_neighbour_result = kdtree
.query(&query)
.nearest_one::<SquaredEuclidean<f32>>()
.execute();
let start = Instant::now();
let file = File::create("./examples/geonames-immutable-tree.postcard.gz")?;
let mut encoder = GzEncoder::new(file, Compression::default());
let serialized = postcard::to_allocvec(&kdtree)?;
encoder.write_all(&serialized)?;
encoder.finish()?;
println!(
"Serialized k-d tree to gzipped postcard file ({})",
ElapsedDuration::new(start.elapsed())
);
let start = Instant::now();
let file = File::open("./examples/geonames-immutable-tree.postcard.gz")?;
let mut decompressor = GzDecoder::new(file);
let mut bytes = Vec::new();
decompressor.read_to_end(&mut bytes)?;
let deserialized_tree: KdTree<f32, u32, Eytzinger, VecOfArenas<f32, u32, 3, 32>, 3, 32> =
postcard::from_bytes(&bytes)?;
println!(
"Deserialized gzipped postcard file back into a k-d tree ({})",
ElapsedDuration::new(start.elapsed())
);
let query = degrees_lat_lng_to_unit_sphere(52.5f32, -1.9f32);
let nearest_neighbour_result = deserialized_tree
.query(&query)
.nearest_one::<SquaredEuclidean<f32>>()
.execute();
assert_eq!(
nearest_neighbour_result.distance,
original_nearest_neighbour_result.distance
);
assert_eq!(
nearest_neighbour_result.item,
original_nearest_neighbour_result.item
);
let nearest = &cities[nearest_neighbour_result.item as usize];
println!("\nNearest city to 52.5N, 1.9W: {nearest:?}");
Ok(())
}